Use Redis to implement locks (Supporting Distributed applications) (organizing network data), redis
Implement locks using Redis (supports distributed applications) 1. Introduction
Use Redis commands such as setnx, expire, and getset to access mutex Resources
The content of this article is network sorting. For details, refer:
Http://www.linuxidc.com/Linux/2014-12/110958.htm
Http://www.jeffkit.info/2011/07/1000/
Http://blog.csdn.net/java2000_wl/article/details/8740911
2. Background
In special business logic, it is necessary to ensure that there is only one thread for one operation at the same time to ensure data consistency. Prevent data from being rewritten multiple times or multiple duplicate data entries.
3. get and set commands
This method is easy to think of. When each request arrives, get is used to determine whether the lock exists. If it does not exist, set is created. This method has one drawback. Because get and set are two Redis requests, there is a latency between them. In a highly concurrent environment, it is possible that the lock has been set by another thread before the set after the get detects that the lock is not saved. Then, the current thread is set again, so that the lock will become invalid. Therefore, this method can only deal with a low concurrency.
Use the setnx and expire commands to implement
When you access resources that require mutual access, use the setnx command to set a lock key. setnx is used to determine whether the lock exists. If it does not exist, the lock is created and a success is returned, if yes, an error is returned. The server returns the error to the client and instructs the client to try again later. The expire command is used to set an expiration time for the lock, which is used to prevent the thread from crash, resulting in the lock being valid all the time, resulting in a deadlock. For example, if the lock validity period is set to 100 seconds, the lock will automatically expire after 100 seconds even if the thread crashes. (In fact, there is also a problem in this place, which is being executed in high concurrency.ExpireCommands occasionally fail (Redis socketLink issue), lock after failureIt will not automatically expire, and the value will always exist. When a deadlock occurs, subsequent retry operations will never succeed!To ensure successful execution, consider executing expire multiple times when the execution fails.)
Setnx lock "lock"
Expire lock 100 // If the lock is successful, set the expiration time
Do work code //Work logic code
Del lock // Delete the lock after the access to the mutex resource ends
Use the setnx and getset commands to add timespan + timeout (recommended)
How to solve the deadlock problem of setnx + expire? The timestamp corresponding to the lock key can be used to determine whether the lock has occurred. If the current time is greater than the lock value, the lock has expired and can be reused.
In this case, you cannot simply delete the lock by DEL, and then SETNX once. When multiple clients detect that the lock times out, they will try to release it, here there may be a race condition. Let's simulate this scenario:
The C0 operation times out, but it still holds the lock. C1 and C2 read the lock check Timestamp and found that it times out.
C1 sends DEL lock
C1 sent the SETNX lock and succeeded.
C2 sends DEL lock
C2 sends the SETNX lock and succeeds.
In this way, C1 and C2 get the lock! A big problem!
Fortunately, this problem can be avoided. Let's take a look at how the C3 client works:
C3 sends SETNX lock to get the lock. Since C0 still holds the lock, Redis returns a 0 to C3.
C3 sends a GET lock to check whether the lock times out. If the lock does not time out, wait or try again.
Otherwise, if the lock times out, C3 tries to obtain the lock through the following operation:
GETSET lock <current Unix time + lock timeout + 1>
If the timestamp obtained through GETSET and C3 still times out, it indicates that C3 is expected to get the lock.
If a client named C4 executed the above operation step faster than C3 before C3, then the timestamp obtained by C3 is a value that has not timed out, c3 does not get the lock as scheduled. You need to wait again or try again. Note that although C3 does not get the lock, it overrides the lock timeout value set by C4, but the impact of this tiny error is negligible.
Note: To make the distributed lock algorithm more stable, the lock-holding client should check whether its lock has timed out before unlocking, and then perform the DEL operation, the client may be suspended due to a time-consuming operation. when the operation is complete, the lock has been obtained by someone else because of the timeout, so you do not have to unlock it.
Pseudocode:
# Get lock
Lock = 0
While lock! = 1:
Timestamp = current Unix time + lock timeout + 1
Lock = SETNX lock. foo timestamp
If lock = 1 or (now ()> (GET lock. foo) and now ()> (GETSET lock. foo timestamp )):
Break;
Else:
Sleep (10 ms)
# Do your job
Do_job ()
# Release
If now () <GET lock. foo:
DEL lock. foo
4. The code is implemented using the setnx and expire commands.
1 public boolean tryLock(String key, int timeout, int expiretime, int sleeptime) throws Exception { 2 3 4 5 Jedis redis = jedisPool.getResource(); 6 7 try { 8 9 long nano = System.nanoTime();10 11 do {12 13 Long i = redis.setnx(key, "key");14 15 jedisPool.returnResource(redis);16 17 if (i == 1) {18 19 redis.expire(key, expiretime);20 21 return Boolean.TRUE;22 23 }24 25 if (timeout == 0) {26 27 break;28 29 }30 31 Thread.sleep(sleeptime);32 33 } while ((System.nanoTime() - nano) < TimeUnit.SECONDS.toNanos(timeout));34 35 return Boolean.FALSE;36 37 } catch (RuntimeException | InterruptedException e) {38 39 if (redis != null) {40 41 jedisPool.returnBrokenResource(redis);42 43 }44 45 throw e;46 47 }48 49 }50 51
Use the setnx and getset commands to add timespan + timeout (recommended code)
1 public boolean tryLock(String key, int timeout, int expiretime, int sleeptime) throws Exception { 2 3 4 5 Jedis redis = jedisPool.getResource(); 6 7 try { 8 9 long nano = System.nanoTime();10 11 12 13 do {14 15 long timestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiretime) + 1;16 17 Long i = redis.setnx(key, String.valueOf(timestamp));18 19 jedisPool.returnResource(redis);20 21 if (i == 1) {22 23 return Boolean.TRUE;24 25 }26 27 String lockVal = getString(key);28 29 if (StringUtils.isBlank(lockVal) || !StringUtils.isNumeric(lockVal)) {30 31 lockVal = "0";32 33 }34 35 if (System.currentTimeMillis() > Long.valueOf(lockVal)) {36 37 lockVal = getAndset(key, String.valueOf(timestamp));38 39 if (StringUtils.isBlank(lockVal) || !StringUtils.isNumeric(lockVal)) {40 41 lockVal = "0";42 43 }44 45 if (System.currentTimeMillis() > Long.valueOf(lockVal)) {46 47 return Boolean.TRUE;48 49 }50 51 }52 53 if (timeout == 0) {54 55 break;56 57 }58 59 Thread.sleep(sleeptime);60 61 } while ((System.nanoTime() - nano) < TimeUnit.SECONDS.toNanos(timeout));62 63 return Boolean.FALSE;64 65 } catch (RuntimeException | InterruptedException e) {66 67 if (redis != null) {68 69 jedisPool.returnBrokenResource(redis);70 71 }72 73 throw e;74 75 }76 77 }