2026/5/23 17:32:06
网站建设
项目流程
怎样把自己做的网页放在网站里,wordpress前端空白,购物网站首页设计,云南招聘网在上一篇文章中#xff0c;深入剖析了Redis的底层数据结构。那其实只是 Redis 的微观世界。今天#xff0c;我们将镜头拉远#xff0c;来到宏观的分布式系统架构中#xff0c;聊聊 Redis 在生产环境中最著名的应用场景——分布式锁。包含如下细节#xff1a;“你这把锁深入剖析了Redis的底层数据结构。那其实只是 Redis 的微观世界。今天我们将镜头拉远来到宏观的分布式系统架构中聊聊 Redis 在生产环境中最著名的应用场景——分布式锁。包含如下细节“你这把锁到底锁在了哪里”“SetNX 为什么要配合 Lua 脚本”“既然有了 SetNX为什么大厂还要用 Redisson”这篇文章我们就来彻底理清这条进化之路。一、 上帝视角这把锁到底锁在了哪里在深入代码之前我们必须先纠正一个常见的架构认知误区。很多初学者容易把系统想象成“糖葫芦串”结构客户端 - 服务器 - Redis - MySQL。这是不对的。在真实的分布式系统中架构更像是一个**“职能协作网络”**。请看上图核心逻辑如下服务器App Server是多台比如 A、B、C 三台机器跑着相同的 Java 代码集群部署。Redis 和 MySQL 是独立设施它们是独立的服务不依附于某台 App Server。三角关系所有的 App Server 共享同一个Redis。所有的 App Server 共享同一个MySQL。注意Redis 和 MySQL 之间通常没有直接连线它们都是由 App Server 来调度的。那么分布式锁到底锁的是什么这就好比三个办事员App Server A, B, C都要去唯一的档案柜MySQL修改同一份文件。为了防止冲突他们在旁边的墙上挂了一个唯一的“令牌”Redis Key。争抢谁先在 Redis 里占到这个 Key谁就拿到了令牌。权限拿到令牌的办事员才有资格去连接 MySQL 修改数据。归还改完数据把 Redis 里的 Key 删掉把令牌让给别人。结论你锁的不是代码而是对共享资源MySQL的操作权。二、 原始时代SetNX 与 Lua 脚本1. SetNX的理解虽然 SetNX 简单但在生产环境简直是“事故制造机”。你的笔记里提到了几个核心坑我用大白话翻译一下坑一死锁TTL的问题没释放场景Server A 拿到锁刚准备去改数据库突然断电了因为没来得及删锁Redis 里的 Key 永远存在。Server B 和 C 永远拿不到锁系统瘫痪。解决必须加TTL过期时间比如 10 秒后自动删。坑二误删删了别人的锁原子性问题可以通过Lua解决场景A 拿到锁TTL 10秒。A 卡顿了FullGC卡了 15 秒。第10秒Redis 发现 A 的锁过期自动删了。B 趁虚而入拿到了锁。第15秒A 醒了任务做完执行DEL删锁。注意A 此时删的是 B 的锁C 发现没锁了也冲了进来。B 和 C 同时在跑锁失效。解决删锁前必须看一眼 Value 是不是自己的 ID。而且**“看一眼”和“删除”必须是原子操作**必须用 Lua 脚本不能分两步写。坑三不可重入场景你的代码里方法 A 拿了锁方法 A 里又调用了方法 B方法 B 也要拿同一把锁。SetNX 会直接报错因为它发现锁已经存在了虽然就是你自己拿的。这不符合 JavaReentrantLock的习惯。2. 为什么要引入 Lua 脚本加锁一条指令搞定了但解锁却是一个大坑。 为了防止**“误删别人的锁”**比如 A 线程卡顿导致锁过期B 线程拿到锁A 醒来后把 B 的锁删了我们在解锁时必须遵循“先判断再删除”的逻辑Java// 伪代码 if (redis.get(lock) 我的机器码) { redis.del(lock); }问题来了如果不加控制上面的get和del是两步操作不具备原子性。 如果 A 线程刚执行完get判断是自己的锁还没来得及del此时发生了 FullGC 或者网络波动锁刚好过期了B 线程拿到了锁。A 恢复后直接执行del还是会把 B 的锁删掉。解决方案Lua 脚本Redis 执行 Lua 脚本是原子性的。我们将“判断”和“删除”写在一个脚本里发送给 RedisLuaif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end只有用了 Lua才能真正保证 SetNX 方案的安全性。3. SetNX 依然存在的硬伤虽然加上 Lua 脚本解决了原子性问题但 SetNX 方案在工业级场景下依然显得“简陋”不可重入Java 的ReentrantLock允许同一个线程多次获取同一把锁但 SetNX 不行因为 Key 只有一份。TTL 进退两难设短了业务没跑完锁丢了导致并发事故。设长了万一服务挂了锁要很久才过期系统吞吐量下降。三、 工业革命Redisson 的完美进化为了解决 SetNX 的硬伤Redisson登场了。它不是什么黑魔法而是把复杂的逻辑封装成了一个成熟的 SDK。1. 解决不可重入从 String 到 HashRedisson 不再使用简单的 String 结构存锁而是利用Hash结构。 它在 Redis 里的存储形式如下Key:lock:product:1001Field:机器UUID : 线程IDValue:1(重入次数)原理当同一个线程再次来抢锁时Redisson 发现 Field 是自己就将 Value1。释放锁时将 Value-1。直到减为 0才真正删除 Key。 这样就完美实现了类似ReentrantLock的可重入特性。2. 解决 TTL 难题看门狗 (WatchDog) 机制这是 Redisson 最核心的卖点。既然我们不知道业务要跑多久那就让锁**“自动续期”**。工作流程当我们调用lock()方法时只要不指定过期时间Redisson 默认给锁设置 30 秒 TTL。同时Redisson 会在后台启动一个定时任务TimeTask每隔 10 秒默认 TTL 的 1/3检查一次。续期定时任务检测到持有锁的线程还在运行就会通过 Lua 脚本把 Redis 里的锁 TTL重新重置为 30 秒。防死锁如果服务宕机了后台的定时任务也没了没人给锁续命30 秒后锁自动过期不会造成死锁。四、 最后的隐患主从一致性与“多节点”方案到这里Redisson 的看门狗和可重入机制似乎已经完美了。但在Redis 主从架构Master-Slave下还有一个物理定律级别的“硬伤”无法解决。1. 致命场景主从切换的时间差Redis 的主从复制是异步的。这意味着当你向 Master 写入数据后Master 会立即告诉你“成功了”然后再在后台慢慢把数据同步给 Slave。这就产生了一个极端的“真空期”A 线程在 Master 拿到了锁写入 Key 成功。Master 宕机此时锁数据还在内存里还没来得及同步给 Slave。Slave 上位Slave 升级为新 Master但它的内存里没有这把锁。B 线程趁虚而入找新 Master 申请锁也成功了。灾难A 和 B 同时持有了锁互斥彻底失效。怎么办只要你用主从架构这个问题就无解。为了解决这个问题Redisson 提出了一个颠覆性的思路放弃主从复制改用“人海战术”见如下场景帮助理解。这就是MultiLock联锁和RedLock红锁的诞生背景。2. 核心原理为什么“放弃主从”反而更安全你可能会问“放弃主从复制那数据怎么备份这不科学啊”这里的“放弃主从”指的是不依赖“Master 同步给 Slave”来保证数据一致性。我们换一种玩法部署 N 个通常是 3 或 5 个完全独立的 Redis Master 节点。它们之间谁也不听谁的没有主从关系就是 5 个平等的“记票人”。客户端去抢锁的时候必须同时去这 5 个节点上“拉票”。3. 方案 AMultiLock (联锁) —— “一票否决制”MultiLock 的逻辑非常简单粗暴完美主义者。定义它将多个独立的 Redis 锁打包成一个“超级锁”。规则所有节点都必须加锁成功才算成功。只要有一个节点失败比如宕机或者被别人占了整个加锁操作就宣告失败并且会把之前已经拿到手的锁全部释放。通俗理解这就好比你要集齐“七龙珠”才能召唤神龙。你去 5 个 Redis 节点上加锁。节点 1~4 都成功了但节点 5 挂了。结果MultiLock 判定失败。你必须把节点 1~4 的锁也退回去。适用场景它主要不是为了解决主从切换问题的而是为了**“同时锁定多个互不相关的资源”**比如我要同时锁定“订单表”和“库存表”这俩必须一起锁住才有意义。但在解决主从问题上它因为要求 100% 存活一旦有一个 Redis 节点坏了整个系统就没法加锁了可用性太差。4. 方案 BRedLock (红锁) —— “少数服从多数”为了解决 MultiLock “坏一个就全死”的问题RedLock 引入了**“容错机制”**。这也是面试中最高频的考点。定义它是基于 MultiLock 的升级版。规则只要有超过半数N/2 1的节点加锁成功就算成功。通俗理解委员会投票假设有 5 台独立的 Redis Master。你跑去申请锁。Redis A 说可以成功。Redis B 说可以成功。Redis C 说可以成功。Redis D 说我挂了失败。Redis E 说我挂了失败。结果3 票赞成2 票失败。3 5/2恭喜你拿到锁了为什么它能解决主从失效问题回到最开始的场景假如 A 拿到了锁在 A, B, C 三台机器上成功。哪怕其中一台机器 A 突然断电了数据丢了。当线程 B 想要来抢锁时它去访问 B, C, D, E。B 和 C 会告诉它“锁被人占了”。D 和 E 说“可以”。结果2 票赞成2 票反对。2 5/2加锁失败结论RedLock 通过空间多台机器换时间利用概率论多台机器同时挂掉的概率极低保证了锁的强一致性。五、 总结分布式锁的“进化金字塔”回顾 Redis 分布式锁的进化历程我们其实是在做一道“安全性 vs 性能”的选择题。进化阶段技术方案核心原理优点缺点适用场景青铜时代SetNX Lua互斥占坑简单无依赖不可重入需自己处理原子性简单的单次互斥操作白银时代Redisson 普通锁Hash 看门狗可重入自动续期主从切换锁丢失绝大多数生产环境容忍极低概率的锁丢失黄金时代Redisson RedLock多节点 过半机制强一致性容忍节点宕机性能最差运维成本高需维护多个Redis涉及金钱交易、决不允许锁失效的核心业务最后的建议在实际开发中90% 的场景直接使用 Redisson 的普通锁白银时代就足够了。为什么因为 Redis 主从切换恰好发生在“锁住的那几毫秒”的概率比中彩票还低。为了这微乎其微的概率去部署 5 台独立的 Redis 实例RedLock往往属于“过度设计”。但如果你的业务是金融转账一分钱都不能差请毫不犹豫地把RedLock搬出来。