提到分布式锁,很多人也许会脱口而出 “redis”,可见利用 redis 实现分布式锁已被认为是最佳实践。这两天有个同事问我一个问题:“如果某个服务拿着分布式锁的时候,redis 实例挂了怎么办?重启以后锁丢了怎么办?利用主从可以吗?加 fsync 可以吗?”
因此我决定深究这个话题。
备注:本文中,因为信息源使用的术语不同,Correctness 与 Safety 分别翻译成正确性和安全性,实际上二者在分布式锁话题的范畴中意思相同。
Efficiency & Correctness
如果想让单机/实例上的多个线程去执行同一组任务,为了避免任务被重复执行,使用本地环境提供的 Lock 原语即可实现;但如果想让单机/实例上,或多机/实例上的多个进程去抢同一组任务,就需要分布式锁。总体来说,对分布式锁的要求可以从两个角度来考虑:
- 效率 (Efficiency):为了避免一个任务被执行多次,每个执行方在任务启动时先抢锁,在绝大多数情况下能避免重复工作。即便在极其偶然的情况下,分布式锁服务故障导致同一时刻有两个执行方抢到锁,使得某个任务被执行两次,总体看来依然无伤大雅。
- 正确性 (Correctness):多个任务执行方仅能有一方成功获取锁,进而执行任务,否则系统的状态会被破坏。比如任务执行两次可能破坏文件结构、丢失数据、产生不一致数据或其它不可逆的问题。
以效率和正确性为横轴和纵轴,得到一个直角坐标系,那么任何一个 (分布式) 锁解决方案就可以认为是这个坐标系中的一个点:
Solutions
在进入分布式锁解决方案之前,必须要明确:分布式锁只是某个特定业务需求解决方案的一部分,业务功能的真正实现是业务服务、分布式锁、存储服务以及其它有关各方共同努力的结果。
本节分别讨论侧重效率和侧重正确性的两类解决方案,并给出相应的实现思路。
For Efficiency
如果任务的执行具备幂等性,或即使少量任务重复执行对整体功能没有影响,开发者就可以选择侧重效率的解决方案。
Design Requirements
侧重效率解决方案的设计要求可以概括如下:
- High Efficiency:抢锁、释放锁操作高效 (这里暂不设定具体的 QPS)
- Weak Safety:在绝大多数时刻,只要持有时间不超过 TTL,只能有一个 client 能取到锁
- Liveness
- Deadlock free:如果 client 抢锁后崩溃或者出现网络分区,其它 client 不能永远等待下去
- No Fault tolerance/Availability:不需要容错机制,