分布式锁的常见实现方式

41 阅读3分钟

引言

大家好啊,今天我们来讲讲分布式锁有哪几种实现方式。一般我们在面试中,通常就只会回答redis使用setnx来做分布式锁,但是面试官如果扩展来问,分布式锁还有哪几种实现方式?估计大部分人都是一脸懵,就算想起其他的也得一些时间才能想起,更不用说在面试这种紧张刺激的场景下了🤓。那么我们今天一起盘点一下分布式锁到底还有什么实现方式,让面试官对我们刮目相看(展现我们的技术宽度)。

基于Redis的分布式锁

原理:利用 Redis 的 SET key value NX PX 命令(原子性)实现加锁。

  • NX:仅当 key 不存在时才设置(保证互斥)
  • PX:设置过期时间(防止死锁)

优点:性能高、延迟低,实现简单

缺点:Redis 主从架构下存在主从切换导致锁丢失的风险(非强一致性),需要处理锁续期、可重入等问题

Redisson 实现(强推)

Redisson 提供了完整的分布式锁实现:

  • Watchdog 自动续期(内置)
  • 可重入
  • 公平锁 / 读写锁
  • RedLock(多节点)

一般项目直接用这个就够了

基于关系型数据库的分布式锁

基于关系型数据库(MySQL,PostgreSQL)也可以做分布式锁,下面有几种常见方案:

方式一:唯一索引 + 插入记录

  • 尝试插入一条带唯一键的记录,成功则获得锁,失败则等待
  • 释放锁时删除记录

方式二:乐观锁(版本号)

update本身就是行级锁,在同一时间只能有一个连接尝试执行这行数据的语句。 这里不加锁,由数据库尝试执行这条语句的时候去检验是否有其他事务修改过,如果被修改,则当前操作失败,由业务层决定是否重试。

  • 适用于更新场景,通过 UPDATE ... WHERE version = old_version 实现

详细的更新语句如下:

ps:只能保护单条记录的更新,不能用于跨表/复杂业务逻辑

UPDATE table_name SET field1 = new_value, version = version + 1 
WHERE id = ? AND version = old_version;

方式三:悲观锁(SELECT FOR UPDATE)

  • 在事务中使用 SELECT ... FOR UPDATE 锁住某行

优点

  • 无需引入额外中间件
  • 熟悉度高

缺点

  • 性能较差(尤其高并发下)
  • 数据库成为瓶颈
  • 需处理死锁、超时等问题

基于ZooKeeper的分布式锁

原理:利用 ZooKeeper 的临时顺序节点(ephemeral sequential znode)实现。

  • 所有客户端在同一个父节点下创建临时顺序节点
  • 只有序号最小的节点获得锁,其他节点监听前一个节点的删除事件

优点

  • 强一致性(ZooKeeper 的 ZAB 协议保证)
  • 支持公平锁、可重入锁
  • 客户端宕机自动释放锁(临时节点)

缺点

  • 性能不如 Redis(写操作需多数派确认)
  • 运维复杂度高

基于Etcd的分布式锁

etcd 分布式锁的核心是:

谁成功在指定 key 上创建了带租约的唯一记录,谁就获得锁;其他竞争者监听该 key,一旦释放(过期或主动删除),就尝试抢锁。

这类似于“抢占 + 监听”模式

原理:利用 Lease(租约)和 Compare-and-Swap(CAS)机制。

  • 通过 txn 事务 + lease 实现原子加锁
  • 锁与租约绑定,租约到期自动释放

优点

  • 强一致性(Raft 协议)
  • 支持 TTL、Watch 机制
  • 可通过顺序前缀(如 lock/00001)实现 FIFO 队列式锁
  • 云原生生态友好(Kubernetes 使用 etcd)

缺点

  • 相对小众,生态工具不如 Redis/ZooKeeper 成熟

基于分布式协调 / raft 本身的锁

比如:consul sessions/kv,raft log 写入 CAS

这个一般在微服务体系中内置

总结:最佳实践建议

  • 高并发、性能敏感 → Redis + Redisson
  • 强一致性、可靠性优先 → ZooKeeper 或 Etcd
  • 避免重复造轮子 → 优先使用成熟客户端(如 Redisson)
  • 注意锁的超时、可重入、死锁、续期等问题