Redis implementation of distributed locks

In Java, I think everyone is familiar with locks. In concurrent programming, we use locks to avoid data inconsistency caused by competition. Usually, we use it with synchronized and Lock.

However, locks in Java can only be executed in the same JVM process. What if in a distributed cluster environment?

1, Distributed lock

Distributed lock is an idea, and there are many ways to implement it. For example, if we regard sand beach as a component of distributed lock, it should look like this:


Step on the beach and leave your footprints, which corresponds to the locking operation. If other processes or threads see footprints on the beach and prove that the lock has been held by others, they wait.


Erasing footprints from the beach is the process of unlocking.

Lock timeout

In order to avoid deadlock, we can set a gust of wind to blow after unit time and erase the footprints automatically.

There are many implementations of distributed locks, such as database-based, memcached, Redis, system files, zookeeper, etc. Their core idea is roughly the same as the above process.

2, redis

Let's first look at how to implement a simple distributed lock through single node Redis.
1. Lock

Locking actually means setting a value for the Key in redis to avoid deadlock and give an expiration time.

SET lock_key random_value NX PX 5000

It is worth noting that:
random_value is the only string generated by the client.
NX stands for setting the key only when the key does not exist.
The expiration time of the PX 5000 setting key is 5000 milliseconds.

In this way, if the above command is executed successfully, it proves that the client has obtained the lock.

2. Unlock

The unlocking process is to delete the Key. However, it cannot be deleted randomly. It cannot be said that the request of client 1 deletes the lock of client 2. At this time, random_ The role of value is reflected.

In order to ensure the atomicity of the unlocking operation, we use LUA script to complete this operation. First judge whether the string of the current lock is equal to the incoming value. If yes, delete the Key and unlock it successfully.
if'get',KEYS[1]) == ARGV[1] then
return 0

3. Realize

First, we introduce Jedis into the pom file. Here, the author uses the latest version. Note that the API may be different due to different versions.


The process of locking is very simple, that is, SET the value through the SET instruction, and return if successful; Otherwise, it will wait circularly. If the lock is not obtained within the timeout time, the acquisition fails.
public class RedisLock {

Logger logger = LoggerFactory.getLogger(this.getClass());

private String lock_key = "redis_lock"; //Lock key

protected long internalLockLeaseTime = 30000;//Lock expiration time

private long timeout = 999999; //Timeout to acquire lock

//Parameters of the SET command 
SetParams params = SetParams.setParams().nx().px(internalLockLeaseTime);

JedisPool jedisPool;

 * Lock
 * @param id
 * @return
public boolean lock(String id){
    Jedis jedis = jedisPool.getResource();
    Long start = System.currentTimeMillis();
            //If the SET command returns OK, the lock acquisition is successful
            String lock = jedis.set(lock_key, id, params);
                return true;
            //Otherwise, if the lock is not acquired within the timeout time after the cyclic wait, the acquisition fails
            long l = System.currentTimeMillis() - start;
            if (l>=timeout) {
                return false;
            try {
            } catch (InterruptedException e) {
    }finally {


Unlock, we can execute a LUA through jedis.eval. Pass the Key of the lock and the generated string as parameters.
* @param id
* @return
public boolean unlock(String id){
Jedis jedis = jedisPool.getResource();
String script =
"if'get',KEYS[1]) == ARGV[1] then" +
" return'del',KEYS[1]) " +
"else" +
" return 0 " +
try {
Object result = jedis.eval(script, Collections.singletonList(lock_key),
return true;
return false;
}finally {

Finally, we can test it in a multithreaded environment. We start 1000 threads to accumulate count. When calling, the key is to generate a unique string. Here, the author uses Snowflake algorithm.
public class IndexController {

RedisLock redisLock;

int count = 0;

public String index() throws InterruptedException {

    int clientcount =1000;
    CountDownLatch countDownLatch = new CountDownLatch(clientcount);

    ExecutorService executorService = Executors.newFixedThreadPool(clientcount);
    long start = System.currentTimeMillis();
    for (int i = 0;i<clientcount;i++){
        executorService.execute(() -> {
            //Obtain the unique ID string through Snowflake algorithm
            String id = IdUtil.getId();
            try {
            }finally {
    long end = System.currentTimeMillis();"Number of execution threads:{},Total time:{},count Number is:{}",clientcount,end-start,count);
    return "Hello";


So far, the implementation of distributed locks for single node Redis has been completed. It is relatively simple, but the problem is also relatively large. The most important point is that locks are not reentrant.

3, redisson

Redisson Is set up in Redis Based on a Java In memory data grid( In-Memory Data Grid). Make full use of Redis Key value database provides a series of advantages based on Java The common interfaces in the utility toolkit provide users with a series of common tool classes with distributed characteristics. As a result, the toolkit originally used to coordinate single machine multithreaded concurrent programs has the ability to coordinate distributed multi machine multithreaded concurrent systems, which greatly reduces the difficulty of designing and developing large-scale distributed systems. At the same time, combined with various characteristic distributed services, it makes further progress One step simplifies the collaboration between programs in a distributed environment.

Compared with Jedis, Redisson is a powerful group. Of course, its complexity comes with it. It also implements distributed locks and contains many types of locks. For more information, see distributed locks and synchronizers

1. Reentrant lock

The Redis distributed lock we implemented above is not reentrant. Let's take a look at how to call reentrant locks in Redisson.

Here, the author uses its latest version, 3.10.1.


First, get the instance of RedissonClient client through configuration, and then get the instance of lock through getLock.
public static void main(String[] args) {

Config config = new Config();

final RedissonClient client = Redisson.create(config);  
RLock lock = client.getLock("lock1");



2. Get lock instance

Let's first look at RLock lock = client.getLock("lock1"); this code is to obtain the lock instance, and then we can see that it returns a RedissonLock object.
public RLock getLock(String name) {
return new RedissonLock(connectionManager.getCommandExecutor(), name);

The RedissonLock constructor mainly initializes some properties.
public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
//Command actuator
this.commandExecutor = commandExecutor;
//UUID string = commandExecutor.getConnectionManager().getId();
//Internal lock expiration time
this.internalLockLeaseTime = commandExecutor.
this.entryName = id + ":" + name;

3. Lock

When we call the lock method, locate lockinterruptible. Here, the logic of locking is completed.
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {

//Current thread ID
long threadId = Thread.currentThread().getId();
//Attempt to acquire lock
Long ttl = tryAcquire(leaseTime, unit, threadId);
// If ttl is empty, the lock acquisition is successful
if (ttl == null) {
//If the lock acquisition fails, subscribe to the channel corresponding to the lock
RFuture<RedissonLockEntry> future = subscribe(threadId);

try {
    while (true) {
        //Try to acquire the lock again
        ttl = tryAcquire(leaseTime, unit, threadId);
        //If ttl is empty, it indicates that the lock has been obtained successfully. Return
        if (ttl == null) {
        //If ttl is greater than 0, wait for ttl time and continue to try to get
        if (ttl >= 0) {
            getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
        } else {
} finally {
    //Unsubscribe from channel
    unsubscribe(future, threadId);
//get(lockAsync(leaseTime, unit));


The above code is the whole process of locking. First call tryAcquire to obtain the lock. If the return value ttl is empty, it proves that locking is successful and returns; if it is not empty, it proves that locking fails. At this time, it will subscribe to the Channel of the lock, wait for the lock release message, and then try to obtain the lock again. The process is as follows:

Acquire lock

What is the process of obtaining locks? Next, we will look at the tryAcquire method. Here, it has two processing methods, one is a lock with expiration time, and the other is a lock without expiration time.
private RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {

//If there is an expiration time, the lock is acquired in the normal way
if (leaseTime != -1) {
    return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);

//First, execute the method of obtaining the lock according to the expiration time of 30 seconds
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(
    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
//If the lock is still held, open the scheduled task to continuously refresh the expiration time of the lock
ttlRemainingFuture.addListener(new FutureListener<Long>() {
    public void operationComplete(Future<Long> future) throws Exception {
        if (!future.isSuccess()) {

        Long ttlRemaining = future.getNow();
        // lock acquired
        if (ttlRemaining == null) {
return ttlRemainingFuture;


Next, look down. The tryLockInnerAsync method actually executes the logic of obtaining locks. It is a LUA script code. Here, it uses a hash data structure.
RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit,
long threadId, RedisStrictCommand command) {

    //Expiration time
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              //If the lock does not exist, set its value through hset and set the expiration time
              "if ('exists', KEYS[1]) == 0) then " +
                  "'hset', KEYS[1], ARGV[2], 1); " +
                  "'pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              //If the lock already exists and the lock is the current thread, increment the value by 1 through hincrby
              "if ('hexists', KEYS[1], ARGV[2]) == 1) then " +
                  "'hincrby', KEYS[1], ARGV[2], 1); " +
                  "'pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              //If the lock already exists but is not this thread, the expiration time ttl is returned
              "return'pttl', KEYS[1]);",
            internalLockLeaseTime, getLockName(threadId));

This LUA code does not seem complicated. There are three judgments:

adopt exists Judge that if the lock does not exist, set the value and expiration time and lock successfully
 adopt hexists Judge that if the lock already exists and the lock is the current thread, it is proved that it is a re-entry lock and the locking is successful
 If the lock already exists, but the lock is not the current thread, it proves that another thread holds the lock. Returns the expiration time of the current lock. Locking failed

After locking is successful, there will be a hash structure in the memory data of redis. Key is the name of the lock; field is random string + thread ID; The value is 1. If the lock method is called multiple times by the same thread, the value is incremented by 1.> hgetall lock1

  1. "b5ae0be4-5623-45a5-8faa-ab7eb167ce87:1"
  2. "1"

4. Unlock

We unlock it by calling the unlock method.
public RFuture unlockAsync(final long threadId) {
final RPromise result = new RedissonPromise();

//Unlocking method
RFuture<Boolean> future = unlockInnerAsync(threadId);

future.addListener(new FutureListener<Boolean>() {
    public void operationComplete(Future<Boolean> future) throws Exception {
        if (!future.isSuccess()) {
        //Get return value
        Boolean opStatus = future.getNow();
        //If NULL is returned, it proves that the unlocked thread and the current lock are not the same thread, and an exception is thrown
        if (opStatus == null) {
            IllegalMonitorStateException cause = 
                new IllegalMonitorStateException("
                    attempt to unlock lock, not locked by current thread by node id: "
                    + id + " thread-id: " + threadId);
        //Unlock succeeded. Cancel the scheduled task that refreshes the expiration time
        if (opStatus) {

return result;


Then we'll look at the unlockInnerAsync method. Here is also a LUA script code.
protected RFuture unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, EVAL,

        //If the lock no longer exists, release the lock release message
        "if ('exists', KEYS[1]) == 0) then " +
            "'publish', KEYS[2], ARGV[1]); " +
            "return 1; " +
        "end;" +
        //If the thread that releases the lock and the thread that already has the lock are not the same thread, null is returned
        "if ('hexists', KEYS[1], ARGV[3]) == 0) then " +
            "return nil;" +
        "end; " +
        //Release the lock once by decreasing hincrby by 1
        //If the remaining times is greater than 0, refresh the expiration time
        "local counter ='hincrby', KEYS[1], ARGV[3], -1); " +
        "if (counter > 0) then " +
            "'pexpire', KEYS[1], ARGV[2]); " +
            "return 0; " +
        //Otherwise, it proves that the lock has been released. Delete the key and publish the lock release message
        "else " +
            "'del', KEYS[1]); " +
            "'publish', KEYS[2], ARGV[1]); " +
            "return 1; "+
        "end; " +
        "return nil;",
Arrays.<Object>asList(getName(), getChannelName()), 
    LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));


The above code is the logic to release the lock. Similarly, it has three judgments:

If the lock no longer exists, pass publish Release the lock release message. The lock is unlocked successfully

If the unlocked thread is different from the thread of the current lock, the unlocking fails and an exception is thrown

adopt hincrby Decrement by 1, first release the lock. If the remaining times are greater than 0, it proves that the current lock is a re-entry lock and the expiration time is refreshed; If the remaining times are less than 0, delete key And release the lock release message. The unlock is successful

So far, the logic of reentrant lock in Redisson has been analyzed. However, it should be noted that the above two implementation methods are for single Redis instances. If we have multiple Redis instances, please refer to the redislock algorithm. For the specific content of the algorithm, please refer to

Tags: Java Redis Distribution

Posted on Tue, 02 Nov 2021 02:56:19 -0400 by scooter41