1, Distributed lock characteristics
When designing distributed locks, you should consider at least some conditions that distributed locks must meet:
1. Mutual exclusion: under the condition of distributed high concurrency, only one thread can obtain a lock at the same time, which is the most basic point;
2. Deadlock: under the condition of distributed high concurrency, if a thread obtains a lock, then hangs, and does not release the lock, so that other threads can never obtain the lock, this is a deadlock. Distributed locks must avoid deadlock;
3. Performance: for shared resources with a large number of accesses, you need to consider reducing the lock waiting time to avoid causing a large number of thread blocking. When designing the lock, it is necessary to consider that the granularity of the lock should be as small as possible and the range of the lock should be as small as possible;
4. Reentrant: the same thread can repeatedly get the lock of the same resource. Reentry lock is very conducive to the efficient utilization of resources.
2, Comparison of Redis, Redis lua scripts and redistribute locking
programme | Implementation principle | advantage | shortcoming |
---|---|---|---|
Redis based command | 1. Lock: execute setnx. If successful, execute expire to add the expiration time. 2. Unlock: execute the delete command | Compared with the implementation of database and distributed system, this scheme is the lightest and has the best performance | 1.setnx and expire are executed in two steps, non atomic operation; If setnx is executed successfully but expire fails, deadlock may occur. 2. The delete command may delete locks not held by the current thread by mistake. 3. Blocking waiting and reentry are not supported |
Redis Lua based script | 1. Lock: execute SET lock_name random_value EX seconds NX Command 2. Unlock: execute Lua script and verify random when releasing lock_ value -- Argv [1] is random_value, Keys [1] is lock_name if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end | The implementation logic is also more rigorous. In addition to the single point problem, the production environment adopts this scheme, and the problem is not big | Lock reentry and blocking waiting are not supported |
Based on redistribution | Combined with redis and lua script | Support lock reentry, blocking wait and Lua script atomic operation | Redisson aims to promote the separation of users' attention to Redis, so that users can focus more on processing business logic. |
3, Redisson principle analysis
1. Acquire lock
/**
- Attempt to acquire lock
- @param lockKey
- @param unit time unit
- @param waitTime maximum wait time
- @param leaseTime automatic release time after locking
- @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; }
}
2. Release the lock
/**
- Release lock
- @param lockKey locks resources
*/
public static void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey); lock.unlock();
}
From the above figure, we can see that redisson locking and unlocking Lua scripts are the most important components of redisson distributed lock implementation
3. Interpretation of redistribution locking Lua script
parameter | Example | meaning |
---|---|---|
Number of keys | 1 | Number of keys |
KEYS[1] | lock_name | Lock name |
ARGV[1] | 60000 | Valid time to hold lock: ms |
ARGV[2] | 58c62432-bb74-4d14-8a00-9908cc8b828f:1 | Unique ID: the unique value of set when obtaining a lock. It is redisson client ID(UUID) + thread ID in implementation |
4, Redisson common locks
1. Reentrant Lock the RLock Java object of Redisson's distributed Reentrant Lock implements the java.util.concurrent.locks.Lock interface and supports automatic expiration unlocking
public void testReentrantLock(RedissonClient redisson) {
RLock lock = redisson.getLock("anyLock"); try { // 1. The most common usage // lock.lock(); // 2. It supports the expiration unlocking function. It will be unlocked automatically after 10 seconds without calling the unlock method to unlock manually // lock.lock(10, TimeUnit.SECONDS); // 3. Try to lock, wait up to 3 seconds, and unlock automatically 10 seconds after locking boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS); if (res) { // success // do your business } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }
}
2. Async hronous Redisson also provides methods for asynchronous execution of distributed locks
public void testAsyncReentrantLock(RedissonClient redisson) {
RLock lock = redisson.getLock("anyLock"); try { lock.lockAsync(); lock.lockAsync(10, TimeUnit.SECONDS); Future<Boolean> res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS); if (res.get()) { // do your business } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { lock.unlock(); }
}
3. Fair Lock Redisson distributed reentrant Fair Lock is also an RLock object that implements the java.util.concurrent.locks.Lock interface. While providing the automatic expiration unlocking function, it ensures that when multiple Redisson client threads request locking at the same time, they are preferentially allocated to the thread that makes the request first.
public void testFairLock(RedissonClient redisson){
RLock fairLock = redisson.getFairLock("anyLock"); try{ // Most common usage fairLock.lock(); // It supports the expiration unlocking function. It will be unlocked automatically after 10 seconds without calling the unlock method to unlock manually fairLock.lock(10, TimeUnit.SECONDS); // Try to lock, wait for 100 seconds at most, and unlock automatically 10 seconds after locking boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { // do your business } } catch (InterruptedException e) { e.printStackTrace(); } finally { fairLock.unlock(); }
}
4. Interlock (MultiLock) the RedissonMultiLock object of Redisson can associate multiple RLock objects into an interlock, and each RLock object instance can come from different Redisson instances
public void testMultiLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){
RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); try { // Lock at the same time: lock1 lock2 lock3. All locks are locked successfully. lock.lock(); // Try to lock, wait for 100 seconds at most, and unlock automatically 10 seconds after locking boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { // do your business } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }
}
5. Red lock the RedissonRedLock object of Redisson implements the locking algorithm introduced by redlock. This object can also be used to associate multiple RLock objects into a red lock. Each RLock object instance can come from a different Redisson instance
public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){
RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); try { // Lock at the same time: lock1, lock2, lock3. If the red lock is successfully locked on most nodes, it will be successful. lock.lock(); // Try to lock, wait for 100 seconds at most, and unlock automatically 10 seconds after locking boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { // do your business } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }
}