分布式锁基本原理和实现方式对比

一、前言:为什么单机锁在分布式系统中失效?

在单体应用中,我们常用 synchronizedReentrantLock 保证线程安全。

但当系统拆分为多个服务实例(如 10 台服务器同时运行订单服务),本地锁无法跨 JVM 生效

此时,必须引入 分布式锁(Distributed Lock) ------ 一种跨进程、跨机器的互斥机制。

本文将带你深入理解分布式锁的核心要求,并对比 Redis、ZooKeeper、数据库 三种主流实现方案的优劣。


二、分布式锁的三大核心要求

一个合格的分布式锁,必须满足:

✅ 1. 互斥性(Mutual Exclusion)

  • 同一时刻,只能有一个客户端持有锁

✅ 2. 避免死锁(Deadlock Free)

  • 即使持有锁的节点宕机,锁也能自动释放(通过超时机制)

✅ 3. 高可用 & 容错性

  • 锁服务本身不能成为单点故障

⚠️ 额外加分项:可重入性公平性高性能


三、实现方式一:基于数据库(最简单,但性能差)

原理:

利用数据库 唯一索引排他锁(FOR UPDATE) 实现互斥。

方案 A:唯一索引
sql 复制代码
-- 创建锁表
CREATE TABLE distributed_lock (
    lock_name VARCHAR(64) PRIMARY KEY,
    expire_time BIGINT
);

-- 获取锁:插入成功即获得锁
INSERT INTO distributed_lock (lock_name, expire_time) 
VALUES ('order_lock', 1712345678);
-- 失败则抛出唯一索引冲突
方案 B:SELECT FOR UPDATE
java 复制代码
// 事务中执行
SELECT * FROM distributed_lock WHERE lock_name = 'order_lock' FOR UPDATE;
// 执行业务逻辑...

✅ 优点:

  • 实现简单,无需额外中间件
  • 强一致性(依赖 DB ACID)

❌ 缺点:

  • 性能极差:DB 成为瓶颈
  • 无自动过期:需额外定时任务清理
  • 主从切换可能丢锁

📌 适用场景:低并发、已有 DB 且无 Redis/ZK 的遗留系统


四、实现方式二:基于 Redis(高性能,但需注意细节)

基础实现(错误示范!):

bash 复制代码
# ❌ 错误:非原子操作
GET lock_key
# 若不存在
SET lock_key "client_1"
EXPIRE lock_key 30

问题SETEXPIRE 不是原子的,若服务在 SET 后 crash,锁永不过期!

✅ 正确实现:使用 SET key value NX EX seconds

bash 复制代码
# 原子获取锁(Redis 2.6.12+)
SET lock_key "client_1" NX EX 30
  • NX:仅当 key 不存在时设置
  • EX 30:30 秒后自动过期

释放锁(需校验 owner):

Lua 复制代码
-- unlock.lua
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end

防止 A 释放了 B 的锁!

高级方案:RedLock(Redis 官方推荐)

  • N 个独立 Redis 节点 请求锁
  • 超过半数成功 + 总耗时 < 锁有效期 → 获取成功

✅ 优点:

  • 性能极高(微秒级)
  • 自动过期防死锁
  • 客户端实现简单

❌ 缺点:

  • CP or AP?:Redis 主从异步复制,主挂可能导致锁丢失(不满足强一致性)
  • RedLock 实现复杂,争议较大(Martin Kleppmann 曾质疑其安全性)

📌 适用场景:高并发、允许短暂不一致的业务(如秒杀、缓存重建)


五、实现方式三:基于 ZooKeeper(强一致,但性能较低)

原理:

利用 ZK 的 临时顺序节点(Ephemeral Sequential Node) 实现公平锁。

流程:

  1. 所有客户端在 /locks 下创建临时顺序节点(如 /locks/lock-0000000001
  2. 客户端检查自己是否是最小序号节点
    • 是 → 获得锁
    • 否 → 监听前一个节点的删除事件(Watcher)
  3. 业务执行完,删除自身节点(或会话断开自动删除)

✅ 优点:

  • 强一致性(ZAB 协议保证)
  • 天然支持公平锁 & 可重入
  • 无死锁风险(临时节点随会话销毁)

❌ 缺点:

  • 性能较低:频繁创建/删除节点 + 网络往返
  • 运维复杂:需维护 ZK 集群
  • 羊群效应:大量 Watcher 可能引发性能抖动(可通过"只监听前驱"优化)

📌 适用场景:对一致性要求极高、并发不极端的场景(如配置管理、选主)


六、三大方案对比总结

维度 数据库 Redis ZooKeeper
实现复杂度 ⭐⭐ ⭐⭐⭐
性能 ❌ 极低 ✅ 极高 ⚠️ 中等
一致性 ✅ 强(ACID) ⚠️ 最终一致(主从异步) ✅ 强(ZAB)
自动过期 ❌ 需手动 ✅ 支持 ✅ 临时节点
公平性 ✅ 支持
高可用 依赖 DB HA Redis Cluster ZK 集群(≥3 节点)
典型场景 低并发遗留系统 秒杀、缓存重建 分布式协调、选主

💡 一句话选型建议

  • 要性能 → 选 Redis
  • 要强一致 → 选 ZooKeeper
  • 已有 DB 且并发低 → 用数据库

七、避坑指南:常见错误实践

❌ 错误 1:Redis 锁不设超时

后果 :服务 crash 后锁永远不释放
正解必须用 EX 设置 TTL

❌ 错误 2:释放锁时不校验 value

风险 :A 释放了 B 的锁,导致并发安全问题
正解Lua 脚本比对 owner

❌ 错误 3:ZooKeeper 使用永久节点

后果 :客户端宕机后锁残留
正解必须用临时节点(Ephemeral)


八、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
初次攀爬者2 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
断手当码农3 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者3 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀3 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Asher05093 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式
凉凉的知识库3 天前
Go中的零值与空值,你搞懂了么?
分布式·面试·go
?Anita Zhang3 天前
联邦学习实战:如何在分布式场景下构建隐私保护机器学习模型
人工智能·分布式·机器学习
tony3653 天前
pytorch分布式训练解释
人工智能·pytorch·分布式
2501_933329554 天前
技术深度拆解:Infoseek媒体发布系统的分布式架构与自动化实现
分布式·架构·媒体
星辰_mya4 天前
消息队列遇到Producer发送慢
分布式·kafka