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

Redisson分布式锁实现原理

1. 加锁机制

核心流程

  1. 尝试加锁
    • 客户端通过执行 Lua脚本 向Redis发送加锁请求,保证原子性操作。
  2. 锁存储结构
    • Redis中存储的锁是一个 Hash结构
      • Key:锁名称(唯一标识)
      • Field:客户端ID(UUID + 线程ID)
      • Value:重入次数(支持可重入锁)
  3. 锁超时时间
    • 默认30秒,超时自动释放(避免死锁)。

Lua脚本

-- KEYS = 锁名称, ARGV = 超时时间(毫秒), 客户端ID
if (redis.call('exists', KEYS) == 0) then
    redis.call('hset', KEYS, ARGV, 1);
    redis.call('pexpire', KEYS, ARGV);
    return nil;
end;
if (redis.call('hexists', KEYS, ARGV) == 1) then
    redis.call('hincrby', KEYS, ARGV, 1);
    redis.call('pexpire', KEYS, ARGV);
    return nil;
end;
return redis.call('pttl', KEYS);

2. 锁自动续期(Watchdog)

看门狗机制

  • 问题:业务执行时间超过锁超时时间,导致锁提前释放。
  • 解决方案
    1. 客户端加锁成功后,启动后台线程(Watchdog)。
    2. 每隔 超时时间的1/3(默认10秒) 自动续期锁。
    3. 业务完成后,Watchdog自动停止。

续期Lua脚本

-- KEYS = 锁名称, ARGV = 超时时间(毫秒), 客户端ID
if (redis.call('hexists', KEYS, ARGV) == 1) then
    redis.call('pexpire', KEYS, ARGV);
    return 1;
end;
return 0;

3. 释放锁流程

释放逻辑

  1. 检查当前线程是否是锁的持有者。
  2. 如果是,重入次数减1;若重入次数为0,删除锁。
  3. 取消Watchdog续期任务。

Lua脚本

-- KEYS = 锁名称, Channel, ARGV = 客户端ID, 释放消息
if (redis.call('hexists', KEYS, ARGV) == 0) then
    return nil;
end;
local counter = redis.call('hincrby', KEYS, ARGV, -1);
if (counter > 0) then
    redis.call('pexpire', KEYS, ARGV);
    return 0;
else
    redis.call('del', KEYS);
    redis.call('publish', KEYS, ARGV);
    return 1;
end;
return nil;

4. 高可用性设计

Redis部署模式

模式特点
单节点简单,适用于低并发场景
主从模式主节点宕机时可能丢失锁(异步复制)
哨兵模式自动切换主节点,提高可用性
集群模式通过哈希槽分配锁,支持高并发和高可用

红锁(RedLock)

  1. 客户端向多个Redis节点(≥3)请求加锁。
  2. 多数节点(N/2+1)加锁成功时,视为成功。
  3. 锁有效时间 = 锁超时时间 - 获取锁总耗时。

5. 核心优势

  • 可重入锁:同一线程可多次获取同一锁。
  • 自动续期:通过Watchdog避免锁超时释放。
  • 高可用:支持多种Redis部署模式。
  • 锁类型丰富:公平锁、非公平锁(RLock)。

6. 注意事项

  1. 网络延迟:可能导致锁超时失效。
  2. 时钟漂移:需使用NTP同步时钟。
  3. 业务超时:依赖Watchdog,需避免业务无限阻塞。

7. 业务逻辑无限阻塞处理方案

代码层面预防

措施实现方式
业务逻辑超时控制使用Future、线程中断机制
合理设置锁超时时间不依赖WatchDog,直接指定超时时间

应急处理

措施工具/技术
监控告警 + 手动释放Redis CLI、Prometheus监控
重启服务Kubernetes健康检查(livenessProbe

http://www.kler.cn/a/583122.html

相关文章:

  • stm32第四天控制蜂鸣器
  • Vue中vfor循环创建DOM时Key的理解之Vue中的diff算法
  • JVM中是如何定位一个对象的
  • 学习计划:第四阶段(第十周)
  • 从Swish到SwiGLU:激活函数的进化与革命,qwen2.5应用的激活函数
  • 嵌入式八股C语言---面向对象篇
  • 以数学建模视角打开软件测试:理论+实战全解析!
  • golang从入门到做牛马:第十六篇-Go语言`range`:循环遍历的“瑞士军刀”
  • ffmpeg打开麦克风,录制音频并重采样
  • 【蓝桥杯—单片机】第十五届省赛真题代码题解析 | 思路整理
  • Microsoft Dragon Copilot:医疗AI革命开启,用语音终结手写病历时代
  • 【Leetcode 每日一题】2012. 数组美丽值求和
  • java 手搓一个http工具类请求传body
  • emacs使用mongosh的方便工具发布
  • 练习:关于静态路由,手工汇总,路由黑洞,缺省路由相关
  • [GHCTF 2025]UPUPUP【.htaccess绕过 XBM/WBMP】
  • 面试题(1)MySQL中的锁
  • UVC摄像头命令推流,推到rv1126里面去
  • Java基础入门流程控制全解析:分支、循环与随机数实战
  • 智慧社区管控大屏,人性化和科技感该如何平衡?