当前位置: 首页 > news >正文

怎么用redis lua脚本实现各分布式锁?Redisson各分布式锁怎么实现的?

一、基础可重入锁(RLock)

完整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)

全流程

  1. 加锁
    • 锁不存在时:创建Hash结构(hset),存储客户端ID与重入计数(初始1),设置超时(pexpire
    • 锁已存在且为当前客户端:重入计数+1(hincrby),刷新超时
    • 锁被其他客户端占用:返回剩余生存时间(pttl),触发客户端阻塞重试
  2. 看门狗续期
    • 后台线程每10秒检查锁持有者,若存活则执行pexpire重置为30秒,避免业务未完成锁过期
  3. 释放锁
    • 非持有者操作忽略(hexists校验)
    • 重入计数>0时仅减计数并刷新超时
    • 计数归零时删除锁(del),并通过publish通知等待线程竞争

二、读写锁(RReadWriteLock)

读锁加锁脚本

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]);

写锁加锁脚本

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)

多节点加锁脚本

-- 与RLock加锁脚本相同(每个节点独立执行)

释放脚本

-- 与RLock释放脚本相同(向所有节点广播)

容错流程

  1. 加锁
    • 向≥5个独立Redis节点发送加锁请求
    • 若多数节点(≥ N/2+1)成功且总耗时 < 锁有效期,视为成功
  2. 释放
    • 向所有节点广播删除命令(即使部分节点未响应)
  3. 争议点
    • 时钟漂移风险:节点时钟不同步可能导致锁有效期计算误差(需依赖单调时钟)
    • 官方建议:奇数独立节点部署(N≥5),容忍少数节点故障

四、公平锁(FairLock)

加锁脚本

-- 清理过期等待线程 
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]: 当前时间戳

公平性实现

  1. 排队机制:线程通过rpush进入队列,lpop按序获取锁
  2. 超时清理:循环检查队列头部线程是否超时(zscore判断),避免死等
  3. 性能代价:吞吐量降低20%-30%(队列维护开销)

五、联锁(MultiLock)

加锁流程

-- 复用RLock脚本(每个锁独立执行)

释放流程

-- 复用RLock释放脚本(遍历所有锁执行)

全流程

  1. 原子加锁
    • 遍历所有锁尝试获取(tryLock),超时时间均分(总超时/N)
    • 若失败数 > 容忍阈值(默认0),立即释放已获锁
  2. 联锁释放
    • 无论单锁是否成功,均尝试释放所有锁

六、核心设计总结

  1. 原子性保障
    • 所有操作封装为Lua脚本,确保exists/hset/pexpire等命令原子执行
  2. 防死锁机制
    • 默认超时 + 看门狗续期(后台线程保活)
  3. 误删防护
    • 释放锁时校验客户端ID(UUID+线程ID)
  4. 容错方案
    • 红锁容忍少数节点故障,联锁确保多资源原子更新

完整脚本实现参考Redisson源码:

  • 可重入锁
  • 公平锁队列实现
http://www.dtcms.com/a/363052.html

相关文章:

  • Higress云原生API网关详解 与 Linux版本安装指南
  • lua脚本在redis中如何单步调试?
  • docker 安装 redis 并设置 volumes 并修改 修改密码(二)
  • MATLAB矩阵及其运算(四)矩阵的运算及操作
  • 互联网大厂求职面试记:谢飞机的搞笑答辩
  • Linux为什么不是RTOS
  • 对矩阵行化简操作几何含义的理解
  • 集群无法启动CRS-4124: Oracle High Availability Services startup failed
  • TSMC-1987《Convergence Theory for Fuzzy c-Means: Counterexamples and Repairs》
  • uni-app 实现做练习题(每一题从后端接口请求切换动画记录错题)
  • Nginx的反向代理与正向代理及其location的配置说明
  • 久等啦!Tigshop O2O多门店JAVA/PHP版本即将上线!
  • SpringBoot3 + Netty + Vue3 实现消息推送(最新)
  • B树和B+树,聚簇索引和非聚簇索引
  • 云计算学习100天-第44天-部署邮件服务器
  • vscode炒股插件-韭菜盒子AI版
  • 小白H5制作教程!一分钟学会制作企业招聘H5页面
  • Linux 环境配置 muduo 网络库详细步骤
  • WPF 开发必备技巧:TreeView 自动展开全攻略
  • gbase8s之导出mysql导入gbase8s
  • WebSocket STOMP协议服务端给客户端发送ERROR帧
  • 串口服务器技术详解:2025年行业标准与应用指南
  • 大文件稳定上传:Spring Boot + MinIO 断点续传实践
  • DevOps部署与监控
  • WPF中的DataContext以及常见的绑定方式
  • Zynq开发实践(FPGA之流水线和冻结)
  • FPGA入门-分频器
  • 【Python - 基础 - 工具】解决pycharm“No Python interpreter configured for the project”问题
  • 【踩坑随笔】VScode+ESP-IDF头文件标红但能正常运行
  • 广播电视制作领域,什么是SMPTE标准?