用法
SpringRedisLockV2 lock = SpringRedisLockV2.builder()
.lockKey(lockKey).waitTime(0L).leaseTime(1L).build();
// 1. 尝试获取锁
boolean isSuccess = Boolean.TRUE.equals(lock.doWithLock(lockContext -> {
// 2.如果获取锁成功,执行业务处理
if (lockContext.isSuccess()) {
...
}
return false;
}));
实现
加锁
// 获取锁失败会跳出等待,例如获取连接失败,操作redis失败场景
protected String tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
String result = "FAIL";
long timeout = unit.toMillis(waitTime);
long internalLockLeaseTime = unit.toMillis(leaseTime);
while(timeout >= 0L) {
result = this.doTryLock(internalLockLeaseTime);
if ("OK".equalsIgnoreCase(result)) {
return "OK";
}
timeout -= 10L;
if (timeout > 0L) {
Thread.sleep(10L);
}
}
return result;
}
protected String doTryLock(long leaseTimeInMillSeconds) {
List<String> keys = new ArrayList();
keys.add(this.lockKeys.get(0));
return (String)this.stringRedisTemplate.execute(SCRIPT_LOCK, keys, new Object[]{this.lockUUID.toString(), String.valueOf(leaseTimeInMillSeconds)});
}
- 构建锁,获取锁的等待时间waitTime默认3秒。锁获取后的超时时间leaseTime默认3秒
- doWithLock创建锁上下文LockContext
- tryLock进入自旋,10毫秒一次重试获取锁。如果获取锁的操作抛出异常会导致等待时间时效,即直接跳出自旋并释放锁unlock
- 获取锁成功后将结果设置到上下文中LockContext
- finally块执行callback业务逻辑入参为lockContext上下文
解锁
protected long unlock() {
List<String> keys = new ArrayList();
keys.add(this.lockKeys.get(0));
return (Long)this.stringRedisTemplate.execute(SCRIPT_UNLOCK, keys, new Object[]{this.lockUUID.toString()});
}
lua脚本
// 加锁
return redis.call('set', KEYS[1], ARGV[1], 'px', ARGV[2], 'nx')
// 解锁
if (redis.call('exists', KEYS[1]) == 0) then
return -1;
end;
if (redis.call('get', KEYS[1]) == ARGV[1]) then
redis.call('del', KEYS[1]);
return 1;
end
return 0
原理
redis提供的一个原子的命令,中间不会穿插其他指令
redis.call('set', KEYS[1], ARGV[1], 'px', ARGV[2], 'nx')
- KEYS[1]:key
- ARGV[1]:value
- ‘px’:key过期的设置
- ARGV[2]:key超时时间
- ‘nx’:SET IF NOT EXIST
Q&A
为什么需要value?
如果user1获取锁key1执行业务逻辑中。user2释放了user1的key1对应的锁。
此时user3获取同一个锁key1执行业务逻辑。那么user1与user3,使用的是同一把锁,但是同时在处理业务逻辑,分布式锁是无效的。
为什么需要超时时间?
不设置超时时间,如果业务处理有bug导致死循环就会出现死锁
为什么需要SET IF NOT EXIST?
不使用SET IF NOT EXIST,那么加锁步骤则是:
- 获取key,判断key是否存在,并且是否超时
- 设置key
如果user1获取key发现不存在,设置key。同一时间user2获取key发现不存在,设置key覆盖了user1的动作