概念
分布式锁就是分布式场景下的锁,比如多台不同机器上的进程,去竞争同一项资源,就是分布式锁。
优秀分布式锁的特征
- 互斥性: 只能有一个竞争者持有锁
- 安全性: 避免死锁发生, 所以需要设置过期时间, 防止意外崩溃无法释放锁
- 对称性: 同一个锁, 加锁者必须负责解锁, 也就是加锁和解锁必须是同一个任务, 不能解除其他任务的锁
- 可靠性: 需要具有一定程度的异常处理和抗压, 容灾能力
分布式锁的实现方式
最简单版本(互斥性)
setnx key value
如果 key 不存在, 则会 将 key 设置为 value, 返回 1
如果 key 存在, 则不会有影响(不会加锁), 返回 0
delete key
通过 delete 解锁
支持过期时间(互斥性, 安全性)
- 使用 redis 中的
expire(失效)
命令, 设置一个 key 的锁的过期时间 - 由于 setnx 和 expire 不是原子操作, 所以 redis 又有原子操作的语句
set key value nx ex seconds
// set a 1 nx ex 3
加上 owner (互斥性, 安全性, 对称性)
- 服务A获取了锁,由于业务流程比较长,或者网络延迟、GC卡顿等原因,导致锁过期,而业务还会继续进行。这时候,业务B已经拿到了锁,准备去执行,这个时候服务A恢复过来并做完了业务,就会释放锁,而B却还在继续执行
- 真实场景中, 会有更多的服务, 也就是出现这种情况会更加多
- 所以规定: 分布式锁需要满足谁申请谁释放原则,不能释放别人的锁,也就是说,分布式锁,是要有归属的。
使用 Lua (检查和删除的原子性)
- 执行完毕后,检查锁,再释放,这些操作不是原子化的。
- Redis + Lua,可以说是专门为解决原子问题而生
可靠性如何保证
容灾考虑
主从容灾
- 配置从节点, 当主节点挂掉的时候使用从节点来接替主节点
- 主从切换本来需要人工成本, 但 redis 通过哨兵模式可以自动切换
- 但是增加从节点也会因为同步时延, 从节点 slave 可能会丢失部分部分数据, 这样分布式锁就失效了
多机部署
- 比如说 redis 的 Redlock 技术, 需要一半以上的分布式机器(一般是奇数个)接受到加锁才算加锁成功
- 加锁过程
- 向 5 个 Redis 加锁
- 如果有超过 3 个 Redis 返回成功, 那么就是获取到了锁, 如果失败, 则需要重新发送请求
- 由于向 5 个 Redis 发送请求,会有一定时耗,所以锁剩余持有时间,需要减去请求时间。这个可以作为判断依据,如果剩余时间已经为 0 ,那么也是获取锁失败
- 使用完之后, 再向 5 个 Redis 发送解锁请求
- 单点 Redis 的所有手段,这种多机模式都可以使用,比如为每个节点配置哨兵模式,由于加锁是一半以上同意就成功,那么如果单个节点进行了主从切换,单个节点数据的丢失,就不会让锁失效了。这样增强了可靠性。
可靠性深究
- 由于分布式锁中的 NPC 问题, 所以没有完全可靠的 分布式锁
Network Delay(网络延迟)
可能因为网络延迟返回的加锁请求时延过长, 但 RedLock 使用 锁剩余持有时间 – 请求时间, 可以一定程度上减缓网络延迟问题
Process Pause(进程暂停)
比如发生 GC, 获取锁之后 GC , GC 结束之后 锁也过期了, 此时两个业务都请求该锁
因为无法判断 GC 何时结束, 所以不好去判断该锁的归属者.
Clock Drift(时钟漂移)
如果竞争者A,获得了 RedLock ,在5台分布式机器上都加上锁。为了方便分析,我们直接假设5台机器都发生了时钟漂移,锁瞬间过期了。这时候竞争者B拿到了锁,此时A和B拿到了相同的执行权限
参考
- 小林coding
- Redis 设计与实现