redis - distributed lock

In java, we can lock with, lock with and unlock with. However, in the case of multiple processes or servers, this locking method can't work because it can't let other clients know whether they are locked or not. So we can use redis, zookeeper, etcd, etc. Redis also has an optimistic lock similar to lock redis - commodity trading The use of WATCH is also shown in, but when there are enough contents in the key, frequent monitoring changes will lead to performance degradation.
The map of java has the putIfAbsent method, which means that if the corresponding key has been assigned, the assignment cannot be continued. In redis, there are similar methods, setnx, SET if Not eXists. If the corresponding key has a value, it cannot be assigned. Therefore, we can use this method as a distributed lock.

// Lock key
static String lockName = "lock:";
static int cnt = 1000;
static CountDownLatch countDownLatch = new CountDownLatch(cnt);

@Test
public void testLock() throws InterruptedException {
    JedisUtils.del(lockName);
    // The time of the lock. When it reaches this time, it will be released
    int lockTime = 1;
    // The timeout time of the lock. When it reaches this time, the acquisition will be abandoned
    long timeOut = 2500;
    for (int i = 0; i < cnt; i++) {
        new Thread(new LockThread(i, lockTime, timeOut)).start();
        countDownLatch.countDown();
    }
    TimeUnit.SECONDS.sleep(3);
}

static class LockThread implements Runnable {
    int lockTime;
    long timeOut;
    int idx;

    public LockThread(int idx, int lockTime, long timeOut) {
        this.idx = idx;
        this.lockTime = lockTime;
        this.timeOut = timeOut;
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String lock = lock(lockName, lockTime, timeOut);
        if (StringUtils.isNotEmpty(lock)) {
            System.out.println(idx + ": Lock acquired");
            //JedisUtils.del(lockName);
        }
    }
}

private static String lock(String lockName, int lockTime, long timeOut) {
    long end = System.currentTimeMillis() + timeOut;
    String value = UUID.randomUUID().toString();
    while (System.currentTimeMillis() < end) {
        long setnx = JedisUtils.setnx(lockName, value);
        // 1 description acquire lock and return
        if (setnx == 1) {
            JedisUtils.expire(lockName, lockTime);
            return value;
        }
        try {
            // If you don't get it, sleep for 100 milliseconds and continue to grab the lock
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return null;
}

Under normal circumstances, the above distributed locks can work, but there are the following problems:

  1. Set the expiration time. If the execution is not completed within the expiration time, other processes will acquire the lock, resulting in multiple processes acquiring the same lock at the same time.
  2. If the process that obtains the lock within the expiration time has crashed, the lock is still not released, resulting in other processes still waiting for expiration.
  3. If there is only one redis server, the server will crash and will not work normally. If redis is the master-slave, process A acquires the lock and crashes when the master server data is not synchronized to the slave server. When process B reads the original slave server that has become the master server, it acquires the lock again because there is no lock, resulting in multiple processes acquiring the same lock at the same time.

Tags: Redis Distributed lock

Posted by cac_azure03 on Mon, 16 May 2022 10:26:00 +0300