How to Control Concurrency by Redis Distributed Lock

Keywords: Jedis Redis MongoDB Java

Interpretation of redis command

Say Redis Distributed locks are implemented by combining setNx commands with getset. Before we talk about them, let's first understand the meaning of setNx and getset. redis The official website explains this.
Note: redis commands are atomic operations

SETNX key value

Set the value of the key to value if and only if the key does not exist.  
If the given key already exists, SETNX does nothing.  
SETNX is short for "SET if Not eXists" (SET if it does not exist).  
Available Version:
1.0.0+ 
Time complexity:
O(1) 
Return value:
Set up successfully, return 1.  
Setup failed, return 0.

redis> EXISTS job                # job does not exist
(integer) 0
redis> SETNX job "programmer"    # job Setup Successful
(integer) 1
redis> SETNX job "code-farmer"   # Attempt to overwrite job, fail
(integer) 0
redis> GET job                   # Not covered
"programmer"
  •  

GETSET key value

Set the value of the given key to value and return the old value of the key.  
When the key exists but is not a string type, an error is returned.  
Available Version:
1.0.0+ 
Time complexity:
O(1) 
Return value:
Returns the old value of the given key.  
When the key has no old value, that is, when the key does not exist, it returns nil.

redis> GETSET db mongodb    # No old values, return nil
(nil)
redis> GET db
"mongodb"
redis> GETSET db redis      # Return the old value mongodb
"mongodb"
redis> GET db
"redis"
  •  

Code examples

Note: For distributed locks algorithm More stable, before unlocking, the client who holds the lock should check once again whether its lock has expired, and then do DEL operation, because the client may hang up because of a time-consuming operation. When the operation is finished, the lock has been acquired by others, so it is not necessary to unlock.  
Let's see that the code involves the following classes. Here, the business logic is related only to defining methods without specific implementation. The key is learning ideas.
 
OrderBiz.Java

/**
 * Use redis lock to control concurrent order grabbing
 * @author fuyuwei
 */
public class OrderBiz {
    public int createOrder(){
        // Validation of parameters and validity before placing an order is not demonstrated here.
        OrderLock<Boolean> orderLock = new RedisOrderLock<Boolean>("pro-12345678901");
        boolean isSyn = orderLock.isSyn(new OrderLockBiz<Boolean>(){
            @Override
            public Boolean createOrder() {
                // Neglect creating order logic
                return null;
            }
        });
        if(!isSyn){
            BizLogger.info("Failure to create an order");
        }
        return 0;
    }
}
  •  

OrderLock.java

public interface OrderLock<T> {
    public boolean isSyn(OrderLockBiz<T> orderBiz);

}
  •  

OrderLockBiz.java

public interface OrderLockBiz<T> {
    public T createOrder();
}
  •  

RedisOrderLock.java

public class RedisOrderLock<T> implements OrderLock<T> {

    //  Lock waiting timeout prevents threads from starving and never has a chance to lock and execute code 
    public static final long timeout = 10000;//ms

    // Lock holding timeout prevents threads from executing indefinitely after locking, so that locks cannot be released 
    public static final long expireMsecs = 10000;// ms

    public String lockKey = "orderLockKey";

    public Jedis jedis;

    private static volatile JedisPool jedisPool;

    public RedisOrderLock(String lockKey) {
        this.lockKey = lockKey;
    }
    /**
     * Initialize redis 
     * @return
     */
    public Jedis getInstance() {
        if(jedisPool == null) {
            synchronized(RedisOrderLock.class) {
                if(jedisPool == null) {
                    JedisPoolConfig config = new JedisPoolConfig();
                    config.setMaxIdle(100);
                    jedisPool = new JedisPool(config,"localhost",6379, 3000,"test");
                }
            }
        }
        return jedisPool.getResource();
    }

    /**
     * Thread-Safe Business Logic Processing
     */
    @Override
    public boolean isSyn(OrderLockBiz<T> orderBiz) {
        jedis = this.getInstance();
        try {
            // Acquisition Lock
            if(acquire(jedis)){
                // Execute order creation logic
                orderBiz.createOrder();
            }else{
                BizLogger.info("waiting other thread creating");
            }
        } catch (Exception e) {
            BizLogger.error(e,"acquire lock failre");
        }finally{
            // Unlock
            this.releaseLock(jedis);
        }
        return false;
    }

    /**
     * accqure lock
     * @param jedis
     * @return
     * @throws InterruptedException
     */
    public synchronized boolean acquire(Jedis jedis){
        boolean locked = false;
        while(timeout > 0){
            long expires = System.currentTimeMillis() + expireMsecs + 1;
            // The lock expires after 10 seconds
            String expiresStr = String.valueOf(expires);
            // Acquisition Lock
            if(jedis.setnx(lockKey, expiresStr) == 1){
                locked = true;
                return locked;
            }
            // No lock was acquired
            String oldValue = jedis.get(lockKey);
            // If the expireMsecs (10 seconds) lock expires, the if judgment cannot be entered, if the lock expires
            if(oldValue != null 
                    && Long.parseLong(oldValue) < System.currentTimeMillis()){
                // If the lock timeout is reset
                String oldValue_ = jedis.getSet(lockKey, expiresStr);
                // The same value indicates that the operation of the same thread succeeds in obtaining the lock.
                if(Long.valueOf(oldValue_) == Long.valueOf(oldValue)){
                    locked = true;
                }else{
                    // Preemptive acquisition of locks by other threads
                    locked = false;
                }
            }
            // The lock has not timed out. Keep waiting
            return false;
        }
    }
    /**
     * Release lock
     * @param jedis
     */
    public synchronized void releaseLock(Jedis jedis){
        try {
            long current = System.currentTimeMillis();  
            // Avoid deleting locks that are not self-acquired
            if (current < Long.valueOf(jedis.get(lockKey)))
                jedis.del(lockKey);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            // Place the used connection in the connection pool sink for other threads to call
            jedisPool.returnResource(jedis);
        }
    }
}

Posted by oldschool on Sat, 22 Jun 2019 14:37:48 -0700