当前位置: 首页 > 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.xdnf.cn/news/1434997.html

相关文章:

  • Unity通过Object学习原型模式
  • ES6和CommonJS模块区别
  • GNU Make | C/C++项目自动构建入门
  • DevOps运维与开发一体化及Kubernetes运维核心详解
  • Aurobay EDI 需求分析:OFTP2 与 EDIFACT 驱动的汽车供应链数字化
  • DataAgent技术解析:数据智能的未来之路
  • LangGraph 上下文工程权威指南:构建智能、感知、有记忆的 AI 代理
  • Ubuntu平台查看.gz格式压缩文件内容以及利用grep命令过滤搜索内容
  • 《浪浪山小妖怪》知识竞赛来袭!测测你是几级影迷?
  • RL【1】:Basic Concepts
  • 情况三:已经 add ,并且也 commit 了
  • 机器人控制器开发(整体架构2 Lerobot介绍)
  • 佛山体彩第二届唱享之夜浪漫收官, 七夕音乐派对全场大合唱!
  • 使用 Gulp + Webpack 打造一个完整的 TypeScript 库构建流程
  • 社区医疗健康管理系统的设计与实现-(源码+LW+可部署)
  • Linux92 shell:倒计时,用户分类
  • [re_2] rpc|http|nginx|protobuf|
  • HBuilder X 4.76 开发微信小程序集成 uview-plus
  • 【Linux我做主】进程退出和终止详解
  • C++编程语言:标准库:第37章——正则表达式(Bjarne Stroustrup)
  • 拷打字节面试官之-吃透c语言-哈希算法 如何在3面拷打字节cto 3万行算法源码带你吃透算法面试所有考题
  • 【完整源码+数据集+部署教程】鸡粪病害检测系统源码和数据集:改进yolo11-bifpn-SDI
  • 前端开发中经常提到的iframe、DOM是什么?
  • WPF中的DataContext以及常见的绑定方式
  • windows下wsl2 ubuntu开发配置
  • 破解人事管理非标化困境:启效云低代码如何助力业务突围?
  • 为什么同步是无线通信的灵魂?WiFi 与 5G 帧结构中的关键技术
  • 创建一个只能直接构造和销毁,但不能被复制和移动的基类
  • burpsuite使用之CaA神器使用
  • 2025年企业级数据服务API平台大全和接入指南