一、基础可重入锁(RLock)
完整Lua脚本
lua
-- 加锁脚本
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[2]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[2]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
-- 释放锁脚本
if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);
if (counter > 0) then
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;
参数说明
KEYS[1]
: 锁名称(如myLock
)KEYS[2]
: 解锁消息通道(redisson_lock__channel:{myLock}
)ARGV[1]
: 客户端ID(格式:UUID:threadId
)ARGV[2]
: 锁超时时间(默认30,000ms)
全流程
- 加锁
- 锁不存在时:创建Hash结构(
hset
),存储客户端ID与重入计数(初始1),设置超时(pexpire
) - 锁已存在且为当前客户端:重入计数+1(
hincrby
),刷新超时 - 锁被其他客户端占用:返回剩余生存时间(
pttl
),触发客户端阻塞重试
- 锁不存在时:创建Hash结构(
- 看门狗续期
- 后台线程每10秒检查锁持有者,若存活则执行
pexpire
重置为30秒,避免业务未完成锁过期
- 后台线程每10秒检查锁持有者,若存活则执行
- 释放锁
- 非持有者操作忽略(
hexists
校验) - 重入计数>0时仅减计数并刷新超时
- 计数归零时删除锁(
del
),并通过publish
通知等待线程竞争
- 非持有者操作忽略(
二、读写锁(RReadWriteLock)
读锁加锁脚本
lua
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
redis.call('hset', KEYS[1], 'mode', 'read');
redis.call('hset', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[2]);
return 1;
end;
if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
local count = redis.call('hincrby', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[2]);
return count;
end;
return redis.call('pttl', KEYS[1]);
写锁加锁脚本
lua
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
redis.call('hset', KEYS[1], 'mode', 'write');
redis.call('hset', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[2]);
return nil;
end;
if (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[2]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
流程特点
- 读锁共享:无锁或已有读锁时直接叠加计数;存在写锁时仅允许持有该写锁的线程重入(写锁降级)
- 写锁互斥:需确保无任何读写锁存在(
mode
字段为write
且客户端ID匹配) - 性能优势:读操作并发量提升
三、红锁(RedLock)
多节点加锁脚本
lua
-- 与RLock加锁脚本相同(每个节点独立执行)
释放脚本
lua
-- 与RLock释放脚本相同(向所有节点广播)
容错流程
- 加锁
- 向≥5个独立Redis节点发送加锁请求
- 若多数节点(≥ N/2+1)成功且总耗时 < 锁有效期,视为成功
- 释放
- 向所有节点广播删除命令(即使部分节点未响应)
- 争议点
- 时钟漂移风险:节点时钟不同步可能导致锁有效期计算误差(需依赖单调时钟)
- 官方建议:奇数独立节点部署(N≥5),容忍少数节点故障
四、公平锁(FairLock)
加锁脚本
lua
-- 清理过期等待线程
while true do
local firstThread = redis.call('lindex', KEYS[2], 0);
if firstThread == false then break end;
local ttl = redis.call('zscore', KEYS[3], firstThread);
if ttl == false or tonumber(ttl) < tonumber(ARGV[4]) then
redis.call('zrem', KEYS[3], firstThread);
redis.call('lpop', KEYS[2]);
else break end;
end;
-- 检查是否可获取锁
if (redis.call('exists', KEYS[1]) == 0)
and (redis.call('llen', KEYS[2]) == 0
or redis.call('lindex', KEYS[2], 0) == ARGV[2])
then
redis.call('lpop', KEYS[2]);
redis.call('zrem', KEYS[3], ARGV[2]);
redis.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 then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
else
local pos = redis.call('lpos', KEYS[2], ARGV[2]);
if pos then
return redis.call('zscore', KEYS[3], ARGV[2]) - ARGV[1] - ARGV[4];
else
redis.call('rpush', KEYS[2], ARGV[2]);
redis.call('zadd', KEYS[3], ARGV[4] + ARGV[1], ARGV[2]);
return ARGV[1];
end;
end;
关键参数
KEYS[2]
: 线程队列(redisson_lock_queue:{myLock}
)KEYS[3]
: 超时有序集合(redisson_lock_timeout:{myLock}
)ARGV[4]
: 当前时间戳
公平性实现
- 排队机制:线程通过
rpush
进入队列,lpop
按序获取锁 - 超时清理:循环检查队列头部线程是否超时(
zscore
判断),避免死等 - 性能代价:吞吐量降低20%-30%(队列维护开销)
五、联锁(MultiLock)
加锁流程
lua
-- 复用RLock脚本(每个锁独立执行)
释放流程
lua
-- 复用RLock释放脚本(遍历所有锁执行)
全流程
- 原子加锁
- 遍历所有锁尝试获取(
tryLock
),超时时间均分(总超时/N) - 若失败数 > 容忍阈值(默认0),立即释放已获锁
- 遍历所有锁尝试获取(
- 联锁释放
- 无论单锁是否成功,均尝试释放所有锁
六、核心设计总结
- 原子性保障
- 所有操作封装为Lua脚本,确保
exists/hset/pexpire
等命令原子执行
- 所有操作封装为Lua脚本,确保
- 防死锁机制
- 默认超时 + 看门狗续期(后台线程保活)
- 误删防护
- 释放锁时校验客户端ID(UUID+线程ID)
- 容错方案
- 红锁容忍少数节点故障,联锁确保多资源原子更新
完整脚本实现参考Redisson源码: