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

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

在单体应用中,我们常用 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)


八、结语

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

相关推荐
若水不如远方7 小时前
分布式一致性(六):拥抱可用性 —— 最终一致性与 Gossip 协议
分布式·后端·算法
睡醒的土豆9 小时前
解决 Kafka 管理工具中文乱码问题
分布式·kafka
SuniaWang12 小时前
《Spring AI + 大模型全栈实战》学习手册系列· 专题二:《Milvus 向量数据库:从零开始搭建 RAG 系统的核心组件》
java·人工智能·分布式·后端·spring·架构·typescript
Hui Baby12 小时前
TIDB分布式数据库提交设想
数据库·分布式·tidb
⑩-13 小时前
RabbitMQ 架构和工作原理?RabbitMQ 延迟队列如何实现?
java·分布式·架构·rabbitmq
国冶机电安装13 小时前
分布式控制系统(DCS)安装:从方案设计到投运验收的完整指南
分布式
飞Link14 小时前
告别 ROS 的臃肿:用 ZeroMQ 构建极速具身智能分布式大脑(附 Python 实战)
开发语言·分布式·python
会算数的⑨15 小时前
演进——从查日志到 AI 自治,企业监控体系的变迁
人工智能·分布式·后端·微服务·云原生
一叶飘零_sweeeet15 小时前
分布式权限体系破局:统一认证授权与 OAuth2.0 全链路架构落地实战
分布式·架构
014-code16 小时前
Dubbo 之 “最速传说”
java·分布式·dubbo