Redisson
API¶
tryLock() 中:
- 第一个参数表示返回 false 前的最大等待时间(在这段时间内会重试)
- 第二个参数表示锁的 TTL
- 第三个参数即时间单位l
无参数锁则直接返回是否获取成功
@Resource
private RedissonClient redissonClient;
@Test
void testRedisson() throws InterruptedException {
RLock lock = redissonClient.getLock("name");
boolean gotLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
if(gotLock()){
try {
/* 执行业务 */
} finally {
lock.unlock();
}
}
}
可重入¶
原理¶
重入:在一个线程中多次尝试获取同一个锁
- 在之前基于
setnx
实现的锁是不可重入的- key 表示锁的名称
- value 表示持锁线程
Redisson 的可重入使用 hash 结构:
使用 lua 脚本来实现原子性
实现¶
加锁脚本
local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];
-- 锁不存在
if(redis.call('exist', key) == 0) then
-- 设置 hash 条目
redis.call('hset', key, threadId, 1);
-- 设置过期时间
redis.call('expire', key, releaseTime);
return 1;
end;
-- 锁已经存在,判断持锁线程
if(redis.call('hexist', key, threadId) == 1) then
redis.call('hincrby', key, threadId, '1');
-- 重入时重置过期时间
redis.call('expire', key, releaseTime);
return 1;
end;
return 0;
解锁脚本
-- 所不是此线程的
if(redis.call('hexist', key, releaseTime) == 0) then
return nil;
end
local cnt = redis.call('hincrby', key, threadId, -1);
if(cnt > 0) then
redis.call('expire', key, releaseTime);
return nil;
end
else
redis.call('del', key);
return nil;
end;
重试与续期¶
- 设置了
leaseTime
:- Redisson 不会启动 watchdog 定时任务。
- 锁会在
leaseTime
时间后自动释放,即使客户端仍持有锁。
- 未设置
leaseTime
:- Redisson 会启用 watchdog 机制,定期续期锁。
- 锁不会因超时自动释放,除非客户端主动释放或崩溃。
如果使用 lock() 且不设置 waitTime,则会一直阻塞等待锁;tryLock() 不设置 waitTime 则不会等待锁,直接返回获取结果
主从一致¶
主从模式¶
- Redis 主节点负责读写
- 从节点只负责读
multi-lock 模式¶
只有在每个节点都成功获取锁才行
总结¶
- 可重入:利用 redis 的 hash 结构,field 为线程的唯一标识,value 为冲入次数
- 可重试:使用信号量和发布订阅机制,在 subscribe 后阻塞等待(不占用 cpu),直到被通知唤醒
- 超时续期:使用 watchdog 机制,每隔 releaseTime / 3 毫秒重置超时时间