分布式锁有哪些实现
分布式锁是分布式系统中用于解决跨节点/进程并发竞争问题的核心机制,确保同一时间只有一个参与者执行临界操作。常见的分布式锁实现方式有以下几类,各有其适用场景和优缺点:
1. 基于数据库的分布式锁
利用数据库的事务和唯一性约束实现互斥,是最基础的分布式锁方案。
实现方式:
- 唯一索引锁:创建一张锁表(如
distributed_lock
),包含lock_key
(锁标识)、holder
(持有者)、expire_time
(过期时间)等字段,其中lock_key
设为唯一索引。
竞争锁时,尝试插入一条lock_key
为目标值的记录,插入成功即获得锁;释放锁时删除记录;若持有者崩溃,通过定时任务清理过期记录。 - 悲观锁:使用
SELECT ... FOR UPDATE
语句,在事务中对特定记录加行锁,其他进程需等待锁释放。 - 乐观锁:基于版本号(
version
字段),更新时检查版本号是否匹配(WHERE version = xxx
),匹配则更新成功(获得锁),否则重试。
优缺点:
- 优点:实现简单,无需依赖额外中间件。
- 缺点:性能较差(数据库易成为瓶颈);悲观锁可能导致死锁;乐观锁需处理重试逻辑;无原生过期机制,需手动设计超时释放。
适用场景:并发量低、业务逻辑简单的场景。
2. 基于Redis的分布式锁
Redis因高性能和单线程特性,是分布式锁的主流选择,核心利用其原子操作实现互斥。
实现方式:
- 基础版(单节点):
用SET lock_key holder NX PX expire_time
命令(NX
:仅当键不存在时设置,PX
:设置过期时间)。- 获得锁:执行
SET
成功则持有锁,失败则等待重试。 - 释放锁:需判断持有者是否为自己(避免误删他人锁),再执行
DEL
(通常用Lua脚本保证原子性)。
- 获得锁:执行
- Redlock算法(多节点):
为解决单节点Redis故障导致的锁失效问题,向多个独立Redis节点(如5个)发起加锁请求,超过半数节点加锁成功且耗时小于锁过期时间,则认为获得锁。释放时需删除所有节点的锁。
优缺点:
- 优点:性能极高(内存操作);支持过期自动释放,避免死锁。
- 缺点:单节点存在“脑裂”风险(主从切换时锁丢失);Redlock实现复杂,且存在理论争议(如网络延迟导致的锁有效性问题)。
适用场景:高并发场景,对性能要求高,可接受短暂一致性风险。
3. 基于ZooKeeper/etcd的分布式锁
基于分布式协调服务的强一致性特性,利用节点特性和事件通知实现锁,适合对一致性要求高的场景。
实现方式(以ZooKeeper为例):
-
临时有序节点:
- 在指定锁路径(如
/locks/my-lock
)下创建临时有序子节点(如/locks/my-lock/lock-0000001
)。 - 所有竞争者创建节点后,获取该路径下的所有子节点,排序后判断自己是否为最小节点:
- 是则获得锁;
- 否则监听前一个节点的删除事件(避免轮询),当前节点删除后重试判断。
- 释放锁:断开连接后,临时节点自动删除(崩溃时也会释放)。
- 在指定锁路径(如
-
etcd实现:
类似ZooKeeper,基于Raft协议保证强一致性,通过Put
操作的prevExist=false
(原子创建)和Watch
机制实现,支持Lease
(租约)机制自动释放锁。
优缺点:
- 优点:强一致性(无锁丢失风险);支持自动释放(临时节点/租约);通过事件通知减少无效轮询。
- 缺点:性能低于Redis(需网络交互和共识过程);部署和维护成本较高。
适用场景:对一致性要求高的场景(如分布式事务、集群选主)。
4. 基于分布式协调工具的锁
除了上述基础组件,一些分布式工具原生提供锁能力:
- Kubernetes Lease:k8s环境专用,基于etcd实现,支持租约机制,适合Pod间协调(如 leader 选举)。
- Chubby:Google开源的分布式锁服务,可靠性极高,适合长期持有锁的场景(如GFS的Master选举),但国内使用较少。
- Consul:基于Raft协议,提供
Lock
和Semaphore
原语,兼具服务发现和分布式锁功能。
5. 基于消息队列的分布式锁
利用消息队列的“独占消费”特性实现简单锁:
- 实现方式:将临界资源标识作为队列名,仅允许一个消费者(排他性消费)处理该队列消息,消费者获得锁,处理完后释放消费权限。
- 优缺点:实现简单,但粒度较粗(基于队列),不适合细粒度锁;存在消息重复消费风险。
分布式锁的核心考量维度
选择分布式锁时,需评估以下特性:
- 互斥性:确保同一时间只有一个持有者。
- 安全性:崩溃时能自动释放锁,避免死锁。
- 可用性:锁服务本身高可用,避免单点故障。
- 性能:加锁/释放锁的耗时,支持的并发量。
- 公平性:是否按请求顺序获得锁(避免饥饿)。
总结
- 高并发、低延迟场景:优先选 Redis(单节点或Redlock)。
- 强一致性、高可靠场景:优先选 ZooKeeper/etcd。
- 简单场景、无中间件:可选 数据库锁。
- k8s环境:优先用 Lease 资源。