Redis 分布式锁的五大深坑与实战解法
在单体架构时代遇到并发问题我们直接上 synchronized 或者 ReentrantLock 就能轻松搞定。但一到微服务、分布式时代这些本地锁就集体罢工了。这时候我们通常会请出 Redis 来救场实现分布式锁。很多人拍脑袋一想Redis 做分布式锁还不简单用 SETNXSet if Not eXists不就行了抢到返回 1没抢到返回 0。但如果你在生产环境真敢这么写马上就会被现实教做人。Redis 分布式锁其实是一路升级打怪的过程里面藏着无数个深坑。今天咱们就用大白话把实现 Redis 分布式锁会遇到的坑以及怎么填坑挨个捋一遍。第一个坑服务宕机死锁诞生了场景线程 A 用 SETNX 抢到了锁开开心心去执行业务代码。结果刚执行到一半这台服务器突然停电宕机了。问题锁还在 Redis 里但原本要释放锁的线程 A 已经“阵亡”了。这把锁永远没人去释放DEL 操作没执行其他服务器上的线程再也抢不到锁整个业务彻底卡死。这就是最经典的死锁。怎么解加一个过期时间TTL。就算服务器挂了只要时间一到Redis 自动把锁清理掉其他线程就能重新抢锁了。第二个坑加锁和设置过期时间不是原子操作场景既然要加过期时间那我就写两行代码呗SETNX lock_key 1 加锁EXPIRE lock_key 10 设置 10 秒过期问题这依然有死锁的风险。万一第一步 SETNX 刚执行完还没来得及执行第二步 EXPIRE服务器就宕机了呢这两步操作不是原子的要么都成功要么都失败中间是可以被打断的。怎么解在 Redis 2.6.12 版本之后官方提供了一个原子的命令把这两个操作合二为一了SET lock_key 1 NX PX 10000这句话的意思是如果 key 不存在就设置NX并且设置 10000 毫秒的过期时间PX一气呵成。第三个坑不小心释放了别人的锁场景现在我们用了原子操作也有了过期时间看起来很完美了对吧看看下面这个流程线程 A 抢到了锁过期时间是 10 秒。线程 A 的业务太复杂或者遇到了网络卡顿执行了 15 秒还没完。第 10 秒的时候Redis 发现锁过期了自动释放了。此时线程 B 跑过来顺利抢到了锁开始执行业务。第 15 秒线程 A 终于执行完了它顺手执行了一句 DEL lock_key 来释放锁。问题线程 A 把线程 B 正在用的锁给删了然后线程 C 又能抢到锁并发彻底乱套了。怎么解解铃还须系铃人谁加的锁只能谁来解。加锁的时候不要随便存个 1而是存一个唯一的标识比如 UUID 或者当前线程的 ID。SET lock_key 唯一标识 NX PX 10000释放锁的时候先查一下这个 key 的值是不是自己的唯一标识。如果是才执行删除。注意“判断是不是自己”和“删除”又是两步操作为了保证原子性这里必须使用Lua 脚本交给 Redis 执行。第四个坑锁到期了但业务还没执行完场景这就紧接着刚才的第三个坑。虽然我们通过唯一标识防止了“误删别人的锁”但核心问题没解决啊线程 A 的业务没执行完锁就被 Redis 提前回收了导致 A 和 B 同时在跑业务这还叫什么锁有人说“那我把过期时间设置长一点设成 1 分钟、1 小时不行吗”不行。万一服务器真挂了别的线程得干等 1 小时才能接盘系统性能直接扑街。怎么解这就需要引入大名鼎鼎的**“看门狗Watch Dog”机制**了。这事儿不用自己写代码直接上工业级开源框架Redisson。它的原理是假设设置锁默认过期时间为 30 秒。当你抢到锁之后Redisson 会在后台偷偷开启一个线程看门狗每隔 10 秒过期时间的 1/3去检查一下“兄弟你业务干完了没没干完的话我帮你跟 Redis 说一声把过期时间重新续回到 30 秒。”这样一来只要你的业务没执行完锁就不会失效如果你的服务器宕机了看门狗也跟着死了没人续期锁到期自然释放。堪称完美第五个坑Redis 主从切换导致锁丢失场景稍微大一点的公司Redis 肯定不是单机而是“主从集群”一主多从。线程 A 在主节点Master抢到了锁。Master 还没来得及把这把锁的数据同步给从节点SlaveMaster 突然死机了。哨兵机制发现 Master 死了赶紧把其中一个 Slave 提拔成新的 Master。这个新的 Master 上压根没有线程 A 的锁数据线程 B 跑来一抢成功获取了锁。并发安全再次崩溃。怎么解为了解决这个问题Redis 的作者提出了Redlock红锁算法。大致原理是搞 5 个相互独立的 Redis Master 节点。线程来抢锁的时候必须在过半的节点比如 3 个以上都加锁成功才算真正抢到了锁。就算挂掉一两个节点也不会影响整体的正确性。但说句实在话实际生产中很少人用 Redlock。为什么因为太重了性能极差而且搭建和维护成本太高。业界的普遍共识是如果你追求绝对的强一致性比如金融级资金转账一分钱都不能差那请你放弃 Redis 锁直接去用Zookeeper做分布式锁ZK 从底层机制上就保证了数据的强一致性。如果是普通的电商秒杀、积分扣减等日常业务直接用Redisson的单机锁或集群锁就足够了极小概率的主从切换丢锁问题靠数据库层面的乐观锁兜底或者人工对账补偿即可没必要为了 0.01% 的概率牺牲 99.99% 的性能。总结抄作业时间看了这么多坑是不是觉得手写 Redis 分布式锁简直是个灾难没关系前人栽树后人乘凉我们只需要记住最终的实战结论绝对不要自己用 SETNX 去手写分布式锁的逻辑坑太多你填不过来。日常 Java 开发直接引入Redisson框架。使用 Redisson 的 RLock它自带了Lua脚本防误删、看门狗自动续期、甚至还支持可重入锁。一行代码搞定RLock lock redisson.getLock(myLock); lock.lock(); try { // 执行业务逻辑 } finally { lock.unlock(); // 别忘了放在 finally 里释放 }如果面试官问你原理把上面说的死锁、误删、原子性、看门狗、主从丢失这 5 个坑挨个讲给他听保准他频频点头直接发 Offer。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2435091.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!