Redis Distributed locking case
Related video tutorials ( From the power node ):https://www.bilibili.com/video/BV1Uz4y1X72A
Download relevant information :http://www.bjpowernode.com/?cnblogs
1. Component dependency
First we have to go through Maven introduce Jedis Open source components , stay pom.xml Add the following code to the file :
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
2. Lock code
Show the code first , Then I'll take you to explain why this is achieved :
public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * Try to get distributed lock * @param jedis Redis client * @param lockKey lock * @param requestId The request id * @param expireTime Beyond the time * @return Success or failure */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } }
You can see , We lock on a line of code :jedis.set(String key, String value, String nxxx, String expx, int time), This set() Method has five parameters :
- The first is key, We use key To be a lock , because key Is the only one. .
- The second is value, What we pass is requestId, Many children's shoes may not understand , Yes key As a lock is not enough , Why use value? The reason is that when we talk about reliability above , In order to solve the fourth condition of the distributed lock, the ringer is needed , By giving value The assignment is requestId, We will know which request this lock was added , When unlocking, it can be based on .requestId have access to UUID.randomUUID().toString() Method generation .
- The third is nxxx, This parameter we fill in is NX, intend SET IF NOT EXIST, When key When there is no , We carry out set operation ; if key Already exist , Then do nothing ;
- The fourth is expx, This parameter we pass is PX, It means we're going to give this key Add an expired setting , The specific time is determined by the fifth parameter .
- The fifth is time, Corresponding to the fourth parameter , representative key The expiration time of .
in general , Execute the above set() Methods lead to only two results :1. No current locks (key non-existent ), Then do the lock operation , And set an expiration date for the lock , meanwhile value Represents the locked client .2. Existing lock , Do nothing .
The children's shoes with a thin heart will find , Our lock code satisfies three conditions described in our reliability . First ,set() Joined the NX Parameters , You can guarantee that if you have key There is , The function will not be called successfully , That is, only one client can hold the lock , Satisfy the mutual exclusion . secondly , Because we set the expiration time for the lock , Even if the lock holder subsequently crashes without unlocking , The lock will also be automatically unlocked when the expiration time is reached ( namely key Be deleted ), A deadlock will not occur . Last , Because we're gonna value The assignment is requestId, Represents the client request ID for locking , Then when the client is unlocked, it can check whether it is the same client . Because we only consider Redis Scenario of stand-alone deployment , So we don't think about fault tolerance .
3. The wrong sample 1
A common example of error is the use of jedis.setnx() and jedis.expire() Combination implementation lock , The code is as follows :
public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) { Long result = jedis.setnx(lockKey, requestId); if (result == 1) { // If the program crashes suddenly here , The expiration time cannot be set , A deadlock will occur jedis.expire(lockKey, expireTime); } }
setnx() What the method does is SET IF NOT EXIST,expire() The way is to add an expiration time to the lock . At first glance it looks like the one in front set() The method gives the same result , But because these are two Redis command , It's not atomic , If the program is running out setnx() And then it collapsed. , Causes the lock to have no expiration time set . Then there will be a deadlock . The reason why someone on the Internet has realized this , Because of the lower version of jedis Multi parameter is not supported set() Method .
4. The wrong sample 2
This kind of error example is more difficult to find problems , And the implementation is more complex . Realize the idea : Use jedis.setnx() Command implementation lock , among key It's a lock ,value It's the expiration time of the lock .
Execution process :1. adopt setnx() Method to try locking , If the current lock does not exist , Returns lock successfully .2. If the lock already exists, get the expiration time of the lock , Compare it to the current time , If the lock has expired , Sets a new expiration time , Returns lock successfully .
The code is as follows :
public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) { long expires = System.currentTimeMillis() + expireTime; String expiresStr = String.valueOf(expires); // If the current lock does not exist , Returns lock successfully if (jedis.setnx(lockKey, expiresStr) == 1) { return true; } // If the lock exists , The expiration time of the lock acquired String currentValueStr = jedis.get(lockKey); if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // Lock expired , Gets the expiration time of the last lock , And set the expiration time of the current lock String oldValueStr = jedis.getSet(lockKey, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // Consider the case of multi-threaded concurrency , Only one thread has the same set value as the current value , It has the right to lock it return true; } } // Other situations , All return locks failed return false; }
So what's the problem with this code ?
1、 Due to the client's own generation expiration time , So we need to force the time synchronization of each client under the distributed environment .
2、 When the lock expires , If multiple clients execute at the same time jedis.getSet() Method , So, although only one client can lock in the end , However, the expiration time of this client's lock may be overridden by other clients .
3、 Locks do not have owner identity , That is, any client can unlock .