Redisson实现Redis分布式锁的原理
Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid)客户端,它提供了丰富的分布式对象和服务,其中分布式锁是其核心功能之一。
基本原理
Redisson的分布式锁实现主要依赖Redis的以下特性:
单线程模型:Redis的单线程特性确保命令执行的原子性
键过期机制:通过EXPIRE命令实现锁的自动释放
Lua脚本:保证复杂操作的原子性执行
核心实现机制
1. 加锁过程
lua
复制
下载
-- Redisson加锁的Lua脚本核心逻辑 if (redis.call('exists', KEYS[1]) == 0) thenredis.call('hset', KEYS[1], ARGV[2], 1)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)redis.call('pexpire', KEYS[1], ARGV[1])return nil end return redis.call('pttl', KEYS[1])
参数说明:
KEYS[1]
:锁的key名ARGV[1]
:锁的过期时间(毫秒)ARGV[2]
:客户端唯一标识(通常由UUID+线程ID组成)
执行流程:
检查锁key是否存在
如果不存在,使用hash结构设置锁,并设置过期时间
如果已存在且是当前客户端持有,则重入计数+1,并刷新过期时间
如果被其他客户端持有,返回锁的剩余生存时间
2. 可重入实现
Redisson通过Redis的Hash结构实现可重入锁:
Hash的field为客户端唯一标识
Hash的value为锁的重入次数
3. 锁续期机制(Watchdog)
java
复制
下载
// Redisson的看门狗线程核心逻辑 private void scheduleExpirationRenewal() {// 每隔锁过期时间的1/3时间续期一次Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) {// 通过Lua脚本续期expireAsync(threadId);}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); }
特性:
默认锁超时时间30秒
每10秒(30/3)检查一次,如果锁仍被持有,则延长锁的过期时间
只在未显式指定leaseTime时启用
4. 解锁过程
lua
复制
下载
-- Redisson解锁的Lua脚本核心逻辑 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) thenredis.call('pexpire', KEYS[1], ARGV[2])return 0 elseredis.call('del', KEYS[1])redis.call('publish', KEYS[2], ARGV[1])return 1 end return nil
执行流程:
检查锁是否存在且是否由当前客户端持有
如果是,则重入计数-1
如果重入计数>0,刷新过期时间
如果重入计数=0,删除锁key并发布解锁消息
关键特性
互斥性:同一时刻只有一个客户端能持有锁
避免死锁:锁自动过期释放 + 看门狗自动续期
可重入性:同一线程可多次获取同一把锁
容错性:Redis节点宕机时,锁会自动释放
公平锁支持:通过Redis的队列实现
锁类型
Redisson提供了多种分布式锁实现:
普通锁(RLock):最基本的可重入锁
公平锁(FairLock):按照请求顺序获取锁
联锁(MultiLock):同时对多个锁加锁
红锁(RedLock):基于多个独立Redis节点的分布式锁
读写锁(RReadWriteLock):读-写分离的锁
红锁(RedLock)算法
Redisson实现了Redis官方推荐的RedLock算法,用于提高分布式锁的可靠性:
获取当前时间(毫秒)
依次尝试从N个独立的Redis实例获取锁
计算获取锁花费的总时间(当前时间 - 步骤1时间)
只有当大多数(N/2+1)节点获取成功
且总耗时小于锁有效时间,才认为获取成功
锁的真正有效时间 = 初始有效时间 - 获取锁花费的时间
如果获取失败,则向所有节点发起解锁请求
使用示例
java
复制
下载
// 获取锁 RLock lock = redisson.getLock("myLock"); try {// 尝试加锁,最多等待100秒,上锁后30秒自动解锁boolean isLocked = lock.tryLock(100, 30, TimeUnit.SECONDS);if (isLocked) {// 执行业务逻辑} } catch (InterruptedException e) {Thread.currentThread().interrupt(); } finally {// 释放锁if (lock.isHeldByCurrentThread()) {lock.unlock();} }
注意事项
网络分区问题:Redis集群发生网络分区时可能导致锁失效
性能考量:频繁的锁操作会增加Redis负担
锁粒度:锁的粒度应尽可能小,避免长时间持有锁
异常处理:确保锁最终能被释放,防止死锁
时钟漂移:在Redis集群中,时钟不同步可能影响锁的有效性