preface
First, the concept and usage scenario of distributed lock
I sorted out a map of Redis knowledge to share with you:
Distributed locking is a way to control synchronous access to shared resources between distributed systems.
In distributed systems, they often need to coordinate their actions. If different systems or hosts on the same system share one or a group of resources, the access to these resources must be mutually exclusive to prevent interference. In this case, distributed locks are required.
Second, redis official website for distributed lock (red lock) definition and Redisson implementation to make a general summary
Take a quick look at this section to understand the official theoretical definition, and you will have a clearer understanding of this section later on.
Redis distributed lock (redlock)
Explain the above five points in Chinese:
Redis redlock algorithm:
In a distributed Redis environment, we assume that there are N Redis masters. These nodes are completely independent of each other and there is no master-slave replication or other cluster coordination mechanism. We ensured that locks would be acquired and released on N instances using the same method as under Redis single instances. Now let’s say we have five Redis master nodes and we need to run these Redis instances on five servers at the same time so that they don’t all go down at the same time.
To get the lock, the client should do the following:
-
1. Get the current time in milliseconds.
-
2. Try to get locks from 5 instances in sequence using the same key and random value (UUID + ThreadId in Redisson). When requesting a lock from Redis, the client should set a network connection and response timeout (which we will refer to several times in the locking section) that is less than the lock expiration time. For example, if your lock expires automatically in 10 seconds, the timeout should be between 5 and 50 milliseconds. This prevents the client from waiting for a response when Redis on the server has already hung up. If the server does not respond within the specified time, the client should try to obtain the lock from another Redis instance as soon as possible.
-
3. The client obtains the lock usage time by subtracting the current time from the start time of obtaining the lock (the time recorded in Step 1). The lock is successful if and only if it is taken from most of the Redis nodes (N/2+1, here 3 nodes) and used for less than the lock expiration time.
-
4. If a lock is obtained, the true validity time of the key is equal to the validity time minus the time used to obtain the lock (calculated in Step 3).
-
5. If, for some reason, the lock fails to be acquired (not in at least N/2+1 Redis instances or the lock has been acquired for longer than the valid time), the client should unlock all Redis instances (even if some Redis instances are not locked at all). Prevents some node from acquiring the lock but the client does not get the response so that the lock cannot be reacquired for a later period of time.
For the above points, redisson’s implementation:
Third, distributed implementation scheme based on Redisson
Before we analyze Redisson’s source code, let’s reiterate that we focus on the four processes of distributed lock locking, lock reentrant, the thread that did not acquire the lock to continue to acquire the lock, and release the lock! I hope I can help you.
Lock reentrant: We assume that the lock time is 30 seconds. Of course, Redisson defaults to 30 seconds, but the service execution time is longer than 30 seconds. If there is no lock reentrant implementation, the lock will fail after 30 seconds, and the service logic will fall into serious consequences that the correctness cannot be guaranteed.
Step 1: Add dependencies
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.5</version>Copy the code
Before formal coding, let’s take a look at the relationship between Redisson’s core classes for implementing distributed locks, as shown below:
Step 2: Formally code the test code
@Slf4j @RunWith(SpringRunner.class) @SpringBootTest(classes = BootIntegrationComponentApplication.class) public class ReidsRedLockTest { private ExecutorService executorService = Executors.newCachedThreadPool(); public RedissonRedLock getRedLock(){ Config config1 = new Config(); Config1. UseClusterServers (.) addNodeAddress (" redis: / / 127.0.0.1:9001 ", "redis: / / 127.0.0.1:9002", "redis: / / 127.0.0.1:9003" , "redis: / / 127.0.0.1:9004", "redis: / / 127.0.0.1:9005", "redis: / / 127.0.0.1:9006"). The setPassword (" 123 "); RedissonClient redissonClient1 = Redisson.create(config1); RLock rLock1 = redissonClient1.getLock("red_lock"); RLock rLock1 = redissonClient1.getLock("red_lock"); // If there are multiple Redis clusters, create the corresponding RLock object as described above and pass it into the following redlock constructor. return new RedissonRedLock(rLock1); @test public void redisRedLock() throws Exception {RedissonRedLock redLock = getRedLock(); int[] count = {0}; for (int i = 0; i < 1000; i++) { executorService.submit(() -> { try { redLock.tryLock(10, TimeUnit.SECONDS); / / lock count [0] + +; Thread.sleep(50000L); } catch (Exception e) {log.error(" add distributed lock Exception: ",e); } finally { try { redLock.unlock(); } catch (Exception e) {log.error(" unlockexception: ",e); }}}); } executorService.shutdown(); executorService.awaitTermination(1, TimeUnit.HOURS); Log.info (" calculated result: {}",count[0]); }}Copy the code
Four, lock process analysis
First we will lock process method call stack list, according to the call steps analysis lock source implementation:
As can be seen from the above call stack, the core method to achieve locking is:
This is a call to lua script execution process, the next to explain the method in detail:
Parameter placeholders in lua scripts:
- KEYS [1] = getName (),
- ARGV[1] = internalLockLeaseTime
- ARGV[2] = getLockName(threadId)
For the getLockName(threadId) method, id = UUID is set when the Redis connection manager is created, as follows
Let’s assume that thread A, after executing the above Lua script, holds the distributed lock, and then for thread A, until the business logic ends and the lock is released, thread A will enter the lock re-entry process, until the business logic completes and the thread voluntarily releases the lock. The thread that does not hold the lock enters the process of scrambling for the lock, until it holds the lock (as for fair competition or unfair competition, we leave a suspense, welcome to see the official comments in the comment area to discuss).
5. Lock reentrant process analysis
Returning to the picture of the method call stack during locking, we can see the method:
The red box in the figure above is the implementation of lock reentrant, explained as follows:
Again using lua scripts,
The specific logic is:
- 0. We assume that thread A holds the lock, and the background thread will re-enter the lock at the time node where the initial expiration time of the lock is an integer divided by 3.
- If checks whether the specified key exists and is held by the current thread
- 2. If it is held by the current thread, the expiration time is reset to the initial expiration time. The default redisson is 30 seconds.
- 3. If the preceding two steps are successful, return 1, which is true. Otherwise return false.
The thread that has not acquired the lock continues to acquire the lock
Returning to thread A’s logic for acquiring the lock, we can see the method through the lock method call stack:
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException
Copy the code
The method is too long, so we cut it up and analyze it.
Based on the above analysis, we know that if a thread fails to acquire the lock for the first time, it will keep trying to acquire the lock until the timeout time we set for acquiring the lock of the Redis instance is exhausted. If the lock is not acquired during this process, it is considered that the lock has failed to be acquired in the Redis instance.
Analysis of lock release process
Let’s first list the lock release process method call stack:
As can be seen from the figure above, the core method in the lock release process is:
Analysis of its Lua script implementation logic:
Analysis shows that after deleting the corresponding key, a message will be published for other threads that have not acquired the lock to subscribe. This logic echoes the lock adding process. After deleting the key, the operation of removing the lock re-entry qualification is performed to ensure that the current thread completely releases the lock.
8. Confusing concepts
A redis instance is not a master or Slave node in a Redis cluster. For a Redis cluster, a cluster is only an instance node in the redLock algorithm. Is determined by the Redis cluster consistency algorithm. The same is true for sentinel mode. Therefore, for RedLock algorithm, if there are N instances, it means N cluster clusters, N Sentinel clusters, and N redis single instance nodes. Rather than N instances in a cluster.