Redis 分布式锁实现原理
0. 前言
1. redis 分布式锁实现原理
1.1 redis 实现分布式锁
1.2 过期时间不精准问题
使用 setnx 命令设置锁时,通常会设置一个过期时间以防止死锁。然而,由于网络延迟或其他因素,准确说是“客户端视角与服务端视角的时钟脱节” 以及 “进程停顿(STW)”,实际的过期时间可能会有所偏差,导致锁提前释放或延迟释放,从而引发并发问题。
一旦发生一锁多持现象,锁的基本性质 —— 独占性遭到破坏。这个可以通过看门狗策略来缓解,但是不能完全解决。
1.3 数据弱一致性问题
当 Redis 挂了,可能会导致锁的状态不一致。例如,如果一个客户端持有锁并且正在执行关键操作,而 Redis 突然崩溃,其他客户端可能会认为锁已经释放,从而同时进入临界区,导致数据不一致。
Redis 挂了导致的状态不一致,本质是主从异步复制导致的锁丢失。Etcd/红锁从存储侧解决了‘锁状态的强一致性’,但依然无法从逻辑侧解决因客户端 STW 导致的一锁多持,后者需配合 Fencing Token 实现终极一致。
2. redis 分布式锁实现源码
2.1 基本框架
在 Redis sdk 之上,基于 Redis 连接池模式,封装了一个简易的 Redis 客户端。

接下来,以分布式锁使用方的进程 id + 协程 id 拼接生成一个字符串,作为使用方的身份标识;在执行加锁操作时,通过 redis 的 setNEX 操作,把生成的字符串设置为 val,并且设定好锁数据的过期时间;
在执行解锁操作时,通过 lua 脚本原子化执行两个步骤:
- get 操作获取 val,查看是否和当前使用方身份一致;
- 倘若身份校验通过,执行 del 操作删除锁数据;
在加锁/解锁操作之外,额外支持一个延长锁过期时间的操作. 该操作同样通过 lua 脚本实现,包括两个步骤:
- get 操作获取 val,查看是否和当前使用方身份一致;
- 倘若身份校验通过,执行 expire 完成锁数据的续期

3. watch dog 实现原理
3.1 续约机制
在加锁成功后,启动一个后台协程,定时检查锁的剩余过期时间。如果发现剩余时间低于某个阈值(例如过期时间的一半),则调用续约函数,延长锁的过期时间。
基本上可以看为就是在 Redis 中实现了一个 etcd 的租约机制。
也就是说 Etcd 租约是 watch 回调型使用的,Redis 锁是主动轮询型用的
3.2 redisson
Redisson 是一个基于 Redis 的 Java 分布式锁实现,上文提到的续约机制就是仿照 Redisson 实现的。
3.3 看门狗 watch dog

下面聊聊看门狗模式的执行流程(其实和 etcd 的租约续约机制非常类似):
- 在执行 redis 分布式锁的上锁操作时,通过 setNEX 指令完成锁数据的设置,携带了一个默认的锁数据过期时间
- 确认上锁成功后,异步启动一个 watchDog 守护协程,按照锁默认过期时间 1/4 ~ 1/3 的节奏(可自由设置),持续地对锁数据进行 expire 续期操作
- 在解锁成功后,会负责关闭 watchDog,回收协程资源. (由于看门狗续期操作会先检查锁的所有权再延期数据,因此实际上使用方只要删除了锁数据,续期操作就不会生效了. 回收看门狗协程是为了规避协程泄漏问题)
4. RedLock实现原理
本文 1.3 小节提出了因 redis 走的是注重于高可用性的 AP 流派,因此存在数据弱一致的问题,进而导致 redis 分布式锁可能出现同时被多方持有的情况. 本章开始,基于 red lock 红锁机制尝试提出这个问题的解决方案。
4.1 多数派原则
所谓多数派原则,就是做出一项决议之前,让所有的参与者进行投票表决,只有投赞同票的人数达到参与者总人数的一半以上成为多数派时,这项决议才被通过。
在红锁 RedLock 实现中,会基于多数派准则进行 CAP 中一致性 C 和可用性 A 之间矛盾的缓和,保证在 RedLock 下所有 redis 节点中达到半数以上节点可用时,整个红锁就能够正常提供服务。
4.2 红锁 redLock
红锁 Redlock 全称 redis distribution lock,是 redis 作者 antirez 提出的一种分布式锁实现方案。加锁成功的判断条件和Raft中向Candidate节点发起投票选举Leader的过程类似,都是基于多数派原则。
在红锁的实现中:
- 我们假定集群中有 2N+1个 redis 节点(通常将节点总数设置为奇数,有利于多数派原则的执行效率)
- 这些 redis 节点彼此间是相互独立的,不存在从属关系
- 每次客户端尝试进行加锁操作时,会同时对2N+1个节点发起加锁请求
- 每次客户端向一个节点发起加锁请求时,会设定一个很小的请求处理超时阈值
- 客户端依次对2N+1个节点发起加锁请求,只有在小于请求处理超时阈值的时间内完成了加锁操作,才视为一笔加锁成功的请求
- 过完2N+1个节点后,统计加锁成功的请求数量
- 倘若加锁请求成功数量大于等于N+1(多数派),则视为红锁加锁成功
- 倘若加锁请求成功数量小于N+1,视为红锁加锁失败,此时会遍历2N+1个节点进行解锁操作,有利于资源回收,提供后续使用方的取锁效率
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


