Official account: Java Xiaokaxiu, website: Javaxks.com
Author: creek ~ source, link: blog.csdn.net/xuan_lu/article/details/111600302
A distributed lock
How to realize distributed lock based on Redis
Why can’t synchronized locks be applied to distributed locks?
Synchronized can solve the synchronization problem, but only one thread can access it at a time, and synchronized locks are JVM locks and only applicable to single point deployment. However, distribution requires the deployment of multiple instances, belonging to different JVM thread objects
Use setnX in Redis to achieve distributed locking.
String lockKey = "product_001_key"; // Semantics: how does not exist in the cache, and return true; / / otherwise, namely lock failure return false Boolean result = stringRedisTemplate. OpsForValue () setIfAbsent (lockKey, "product_001_lock"); if (! Result) {/ / not lock is successful, it returns prompt etc} try {} the catch () {} finally {/ / releases the lock stringRedisTemplate. Delete (lockKey); }Copy the code
For the above set distributed lock to think about the problem?
1. If the server breaks down suddenly, the lock cannot be released.
Solution: Set the timeout period.
String lockKey = "product_001_key"; Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock"); / / set the lock timeout 30 s stringRedisTemplate. Expire (lockKey, 30, TimeUnit. SECONDS); if (! Result) {/ / not lock is successful, it returns prompt etc} try {} the catch () {} finally {/ / releases the lock stringRedisTemplate. Delete (lockKey); }Copy the code
2. If the server breaks down between locking and setting the timeout period, the server will still be locked.
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock"); / / -- -- -- -- -- - server goes down, the timeout is not set success -- -- -- -- -- -- -- / / set the lock timeout 30 s stringRedisTemplate. Expire (lockKey, 30, TimeUnit. SECONDS);Copy the code
Solution: Atomic operation, i.e., lock and timeout at the same time;
That is, the above code is combined into one operation:
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"product_001_lock", 30, TimeUnit.SECONDS)
Copy the code
3. Is the timeout setting reasonable? That is, thread execution time and lock timeout time are not the same.
Scenario: Assume that the lock timeout is set to 10s.
In the scenario of high concurrency, the execution time of thread A is 15s, and Redis releases the lock added by thread A according to the timeout time. Then thread B acquires the lock and locks it successfully. At this point, thread A completes its execution. Finally code block is executed to release the lock.
Solution: Set the thread random ID, and determine whether the lock is added by the current thread when releasing the lock. Even if thread A passively releases its lock due to thread timeout, at least ensure that the current timeout thread will not release the lock added by other threads. However, there are concurrency problems when the thread execution time is longer than the set timeout.
String lockKey = "product_001"; String clientId = UUID.randomUUID().toString(); // Set the timeout. And the lock and the thread ID Boolean result = stringRedisTemplate. OpsForValue () setIfAbsent (lockKey, clientId, 30, TimeUnit. SECONDS) ` if (! } try{}catch() {}finally{// release lock: The lock thread ID is the same as the current executing thread ID, Are allowed to release the lock the if (clientId. Equals (stringRedisTemplate. OpsForValue () get (lockKey))) {stringRedisTemplate. Delete (lockKey); }}Copy the code
4. The solution in the above scenario is to renew the thread lock timeout
Solution: When the lock is successfully added, start a background thread and check whether the current thread still holds the lock every 10s (custom). If the current thread holds the lock, it will continue for 30 seconds
Redission implements distributed locking
Implementation principle and process:
String lockKey = "product_001";
//获取锁对象,并未加锁
RLock redissonLock = redisson.getLock(lockKey);
try {
// **此时加锁**,实现锁续命功能
redissonLock.lock();
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock + "");
} else {
System.out.println("扣减失败,库存不足");
}
}finally {
//释放锁
redissonLock.unlock();
}
Copy the code
conclusion
To sum up, the design and implementation of distributed lock should meet the following conditions:
\1. Mutual exclusion; Only one client can hold the lock at any time.
\2. Deadlock cannot occur; Even if one thread crashes while holding the lock and does not unlock it, it is guaranteed that subsequent threads can lock it.
\3. Lock and unlock must be the same thread.