一、核心问题与解决方案
问题本质
Redisson解决方案
- 客户端唯一ID:UUID+线程ID作为锁标识
- Lua脚本原子操作:校验+删除一步完成
- 看门狗机制:后台线程定期续期
- 发布订阅:锁释放通知避免无效轮询
二、源码实现解析
1. 加锁流程(Lock操作)
核心Lua脚本
-- KEYS[1]: 锁key
-- ARGV[1]: 锁过期时间(毫秒)
-- ARGV[2]: 客户端唯一标识(UUID:threadId)-- 锁不存在时加锁
if (redis.call('exists', KEYS[1]) == 0) thenredis.call('hset', KEYS[1], ARGV[2], 1) -- 使用hash存储redis.call('pexpire', KEYS[1], ARGV[1])return nil
end-- 锁存在且是当前线程持有(重入锁)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) thenredis.call('hincrby', KEYS[1], ARGV[2], 1) -- 重入计数+1redis.call('pexpire', KEYS[1], ARGV[1])return nil
end-- 返回锁剩余生存时间
return redis.call('pttl', KEYS[1])
看门狗启动逻辑(RedissonLock类)
private void scheduleExpirationRenewal(long threadId) {// 创建定时任务Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) {// 检查锁是否仍被当前线程持有RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {// 异常处理return;}if (res) {// 递归调用,实现周期性续期scheduleExpirationRenewal(threadId);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 默认30s/3=10s执行一次
}
续期Lua脚本
-- KEYS[1]: 锁key
-- ARGV[1]: 续期时间(默认30s)
-- ARGV[2]: 客户端唯一标识-- 检查是否仍是当前线程持有锁
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then-- 续期redis.call('pexpire', KEYS[1], ARGV[1])return 1
end
return 0
2. 解锁流程(Unlock操作)
解锁Lua脚本
-- KEYS[1]: 锁key
-- KEYS[2]: 发布订阅频道
-- ARGV[1]: 释放锁消息(0L)
-- ARGV[2]: 锁过期时间
-- ARGV[3]: 客户端唯一标识-- 锁不存在(可能已过期)
if (redis.call('exists', KEYS[1]) == 0) then-- 发布解锁消息redis.call('publish', KEYS[2], ARGV[1])return 1
end-- 非当前线程持有
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) thenreturn nil
end-- 减少重入计数
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1)
if (counter > 0) then-- 重入次数>0,仅更新过期时间redis.call('pexpire', KEYS[1], ARGV[2])return 0
else-- 完全释放锁redis.call('del', KEYS[1])-- 发布解锁消息redis.call('publish', KEYS[2], ARGV[1])return 1
end
锁释放通知机制
// RedissonLock.unlockAsync方法
protected RFuture<Boolean> unlockAsync(long threadId) {// 执行解锁Lua脚本RFuture<Boolean> future = unlockInnerAsync(threadId);future.onComplete((res, e) -> {// 取消看门狗任务cancelExpirationRenewal(threadId);if (e != null) {// 异常处理return;}if (res == null) {// 锁非当前线程持有throw new IllegalMonitorStateException();}});return future;
}
3. 锁等待机制
三、完整工作流程
加锁流程
解锁流程
四、关键设计亮点
-
可重入锁设计:
- 使用Hash结构存储
clientId:重入次数
- 避免同一线程多次加锁导致死锁
- 使用Hash结构存储
-
锁续命机制:
- 默认每10秒续期一次(internalLockLeaseTime/3)
- 续期时间可配置(默认30秒)
-
高效等待机制:
// 订阅锁释放通知 RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {// 超时处理 }
-
容错处理:
- 网络异常时自动重试
- 加锁超时自动取消
- 看门狗线程异常自动终止
五、最佳实践建议
-
锁命名规范:
// 使用业务前缀+资源ID RLock lock = redisson.getLock("order:pay:" + orderId);
-
超时时间设置:
// 根据业务最大耗时设置 lock.lock(10, TimeUnit.SECONDS);
-
避免长事务:
- 超过30秒的业务考虑拆分
- 监控看门狗日志,警惕续期失败
-
集群环境特别配置:
Config config = new Config(); config.useClusterServers().setCheckSlotsCoverage(false); // 避免slot覆盖检查
六、性能优化点
-
减少网络往返:
- 所有关键操作用Lua脚本实现
- 单次请求完成多个操作
-
避免无效轮询:
- 通过发布订阅通知等待线程
- 精确控制重试时机
-
轻量级看门狗:
- 定时任务而非持续轮询
- 空闲时自动释放资源
通过Redisson的这套实现,分布式锁在保证安全性的同时,实现了高性能和高可用性,是生产环境的首选方案。