Reference website:
https://juejin.cn/post/6844903543590092814
https://www.jianshu.com/p/533072fa4d52
https://www.cnblogs.com/zhili/p/redisdistributelock.html
https://juejin.cn/post/6844903830442737671#heading-5
Copy the code
1. Common conditions of distributed locks
(1) Mutual exclusion. A critical section can only be executed by one thread per client at any time. (2) Reentrancy. The thread that acquired the lock can acquire it repeatedly. (3) Lock acquisition and lock release must be the same thread. (4) Automatically release the lock. The lock acquiring thread crashes without actively releasing the lock, and the lock can still be acquired by other threads.
understand
- Conditions 1, 2, and 3 require the lock to record the machine + thread that acquired the lock.
- Condition 2 requires that locks be counted the number of times the same thread is locked
- Condition 4 requires the lock to have an expiration time.
2. Distributed lock based on Redis
2.1 Distributed lock design based on Redis (reentrant lock is not supported)
The advantages and disadvantages
Advantages: With the help of redis high performance characteristics, high performance distributed lock. Disadvantages:
- The locking failure requires multiple service retries. (ZK only needs to register listeners)
- Lock expiration time is difficult to set. This is because unlike ZK, you can create temporary nodes that automatically remove locks, so you need to set an expiration time to prevent missed lock releases. However, if the expiration time is set, it is assumed that the current business program crash failed to release the lock. If the expiration time is too long, subsequent requests cannot be locked. If the expiration time is too short, if the previous request processing event is long and still holds the lock, multiple requests can access the resource at the same time, violating the mutual exclusion.
- In clustered mode, master/slave synchronization may result in multiple requests acquiring locks because the Redis cluster is not highly consistent.
Locking logic
Unlock the logic
Implement the design
(1) Design of key and value
key = lock.{resource};
value = {machineId}_{threadId};
Copy the code
(2) Lock and set the timeout action
To ensure atomicity of set actions and expire actions, there are two main approaches: I. Use ex and Nx when setting
SET key value [EX seconds] [PX milliseconds] [NX|XX]
Copy the code
Ii. Use lua scripts to execute set and expire
(3) Lock release operation
Need to ensure get action, DEL action atomicity, rely on Lua script to achieve atomicity.
3. Redisson implements distributed locking
(1) Distributed lock implementation (reentrant lock and retry mechanism are not implemented)
public <V> V executeReadWriteLuaScript(String luaScript, RScript.ReturnType returnType, List<Object> keys,
Object[] values) {
RScript rScript = getScript();
return rScript.eval(RScript.Mode.READ_WRITE, luaScript, returnType, keys, values);
}
public boolean lock(String resource, Object machineId, Object threadId, long expireTime) {
String key = "lock." + resource;
String value = machineId.toString() + "_" + threadId.toString();
return lock(key, value, expireTime);
}
public boolean lock(String key, String value, long expireTime) {
RBucket<String> bucket = getBucket(key);
return bucket.trySet(value, expireTime, TimeUnit.SECONDS);
}
public void unlock(String resource, Object machineId, Object threadId) {
String key = "lock." + resource;
String value = machineId.toString() + "_" + threadId.toString();
unlock(key, value);
}
public void unlock(String key, String value) {
String luaScript = "local value = redis.call('GET', KEYS[1]); " +
"if (value == ARGV[1]) then " +
" redis.call('DEL', KEYS[1]); " +
"end ";
executeReadWriteLuaScript(luaScript, RScript.ReturnType.VALUE,
Lists.newArrayList(key),
Lists.newArrayList(value).toArray());
}
Copy the code
(2) Test the code
@Test public void testDistributedLock() { Long machineId1 = 123L; String resource = "resource"; Runnable r = () -> { if (redisServiceWithRetry.lock(resource, machineId1, Thread.currentThread().getId(), 5)) { try { System.out.println(Thread.currentThread().getName() + " locked"); } catch (Exception e) { e.printStackTrace(); } finally { redisServiceWithRetry.unlock(resource, machineId1, Thread.currentThread().getId()); System.out.println(Thread.currentThread().getName() + " unlocked"); } } else { System.out.println(Thread.currentThread().getName() + " fail to lock"); }}; Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); }}Copy the code
Results: