preface

I believe you are all using distributed services, it must be impossible to circumvent the problem of concurrent data update in distributed services!

A single system can easily think of Java locks such as Synchronize, ReentrantLock, and so on. What about distributed systems?

Using distributed locks, of course.

If you do not know what distributed lock is, it is recommended to see the teacher Huperzia’s assault class or search the relevant information on the Internet.

When using Redis as a distributed lock, the most commonly used framework currently is Redisson.

Of course, Redisson is not only used as a lock, but also has many other functions, you can check the official documentation, more hands-on practice.

Let’s start taking notes about Redisson! Correct mistakes are welcome. 🙏 🏻

Environment configuration

  • Pseudo cluster built by local environment:

  • Redisson 3.15.6

Different versions may be different, but the core idea will not change too much, if the change is great, I hope you can leave a message.

<dependency dependency> <groupId>org.redisson</groupId> </artifactId> redisson</artifactId> <version>3.15.6</version> </dependency>
  • Project preparation

A simple Maven project that requires only one Main method.

Reentrant lock and lock

At the lock.lock() breakpoint, act as source entry.

Lock by default and no parameters are passed. But it’s going to set leaseTime = -1. This leaseTime means the time of locking.

The rest is all we have to do.

Before we call tryAcquire, we have an extra parameter threaDid, which is the ID of the current thread, positive of type long.

Asynchronous lock

Go directly to the tryAcquireasync asynchronous locking method.

We already said that leaseTime is -1, so this is going to go into the following method.

At this point, a few parameters are clear:

  1. WaitTime: – 1;
  2. InternalLockleaseTime: Use the default time of 30,000ms;
  3. TimeUnit.milliseconds: MILLISECONDS;
  4. ThreaId: thread ID;
  5. RedisCommands. EVAL_LONG: eval.

The documentation for the Redis eval command can be read:
https://redis.io/commands/eval

Locking logic

The actual lock is actually a Lua script.

Let’s start with the Lua script’s parameters:

  1. Keys [1] : getRawName(); Keys [1] : getRawName();
  2. ARGV[1] : Unit. toMillis(leaseTime), millisecond lock time, say 30000;
  3. ARGV[2] : getLockName(threaID) is a string concatenated between a UUID and a thread ID, such as 931573de-903e-42fd-baa7-428ebb7eda80:1.

Because you are using a Lua script, you can ensure that this piece of Lua script is atomic.

Analysis of the first lock:

  1. The exists command determines whether Redis Anylock exists;
  2. Does not exist, use hincrby command, create anyLock data;
  3. Set an expiration time for anyLock.

The format of the data in Redis after locking is:

The Hash data structure for Redis can be read:
https://redis.io/topics/data-…

A bit more abstractly, we can think of it as anyLock with a K-V structure hanging below it:

"anyLock":{
    "f400aad5-4b1f-4246-a81e-80c2717c3afb:1":"1"
}

Execute the script

The next step is to make the request to execute the Lua script. The only thing to notice is that there is a hash slot route.

This code is called in the CommandAsyncService# EevalWriteAsync method to get a NodeSource.

Of course, there is only one slot in the NodeSource.

The slot value is calculated using the CRC16 algorithm for the locked key.

// 16384 int result = crc16.crc16 (key.getBytes()) % MAX_SLOT;

What’s the use of this block for calculating a slot?

Keep tracking!

BaseRedisBatchExecutor# AddBatchCommandData gets the solt from the source and gets the master slaveEntry.

This is the node that gets the Redis key.

reentrant

Since it’s a reentrant lock, this block supports reentrancy, so let’s see how reentrancy is guaranteed.

  1. The exists command determines whether the Redis key field exists;
  2. The value of the field corresponding to the key is automatically increased by hincrby.
  3. Sets the expiration time for the current Redis key.

Lock mutex

Two cases have been verified above:

  1. Redis Key does not exist;
  2. Redis key and key field exist.

The remaining case is the case where the key exists, but the field does not.

UUID: threaId () {field: UUID: threaId ();} This directly returns the remaining time of the current lock.

conclusion

This paper mainly introduces the lock adding, lock reentrant and lock mutex logic of Redisson reentrant lock.

The core focus is on the Lua script. You also need to understand Redis’s Hash data structure.

Also keep in mind that 30s is used by default when the locking time is not specified.

Finally, a figure illustrates the locking logic of this article.

Related to recommend

  • How does Spring resolve cyclic dependencies when proxying dynamically? Why use three-level caching?
  • How does Spring resolve circular dependencies?
  • How do you deal with Spring self-invoking transaction invalidation?