基于 2025 年分布式系统最佳实践,以下从原理、实现到生产选型,深度解析三种主流分布式锁方案:
一、Redis RedLock:多主节点的安全性博弈
RedLock 是 Redis 作者 antirez 提出的多节点分布式锁算法,旨在解决单点 Redis 的故障安全问题,但其在分布式系统社区存在较大争议。
1. 核心算法原理
RedLock 要求在多个独立 Redis 主节点(通常 5 个)上同时获取锁,通过多数派机制保证安全性:
获取锁流程:
释放锁流程:
- 向所有节点发送 Lua 脚本删除锁(校验 value 防止误删)
// RedLock 伪代码实现
public boolean tryLock(String resource, String value, int expireTime) {
int quorum = N / 2 + 1; // 多数派阈值
int successCount = 0;
long startTime = System.currentTimeMillis();
for (RedisNode node : redisNodes) {
if (node.set(resource, value, "NX", "PX", expireTime)) {
successCount++;
}
}
long elapsed = System.currentTimeMillis() – startTime;
// 大多数成功且未超时
return successCount >= quorum && elapsed < expireTime;
}
2. 争议与缺陷
Martin Kleppmann 的质疑(《How to do distributed locking》):
- 时钟漂移:若 Redis 节点时钟不同步,可能导致锁提前过期,出现双主问题
- GC 停顿:客户端获取锁后若发生 GC 停顿,锁已过期但客户端不知情,继续操作导致并发问题
- 网络延迟:获取锁过程中若网络抖动,可能导致时间计算不准确
Redis 作者的辩护:
- 通过延迟启动(delay start)避免时钟漂移
- 承认 RedLock 非绝对安全,但在大多数场景下足够安全
3. 生产建议
| 适用场景 | 对性能要求极高、可接受极低概率并发问题的场景(如缓存重建) |
| 不推荐场景 | 金融交易、库存扣减等强一致性要求场景 |
| 替代方案 | 使用 Redis 单节点 + Lua 脚本(Redisson),或 Zookeeper |
二、Zookeeper 临时节点:CP 系统的强一致性锁
Zookeeper 通过临时顺序节点(EPHEMERAL_SEQUENTIAL) + Watcher 机制实现分布式锁,是强一致性场景的首选。
1. 核心实现机制
锁结构设计:
- 为每个锁创建持久节点(如 /locks/order_lock)
- 客户端在锁节点下创建临时顺序子节点(如 /locks/order_lock/lock-00000001)
获取锁流程:
释放锁流程:
- 正常释放:客户端主动删除节点
- 异常释放:客户端会话断开(Session Timeout),Zookeeper 自动删除临时节点
// Curator 框架实现(生产推荐)
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order_lock");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 执行业务逻辑
}
} finally {
lock.release(); // 确保释放
}
Zookeeper 原生实现:
public class ZkDistributedLock {
private String lockPath;
private String currentNode;
public void lock() throws Exception {
// 1. 创建临时顺序节点
currentNode = zk.create(lockPath + "/lock-",
data, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 2. 获取所有子节点并排序
List<String> children = zk.getChildren(lockPath, false);
Collections.sort(children);
// 3. 判断是否最小节点
if (currentNode.endsWith(children.get(0))) {
return; // 获取锁成功
}
// 4. 监听前一个节点
String prevNode = children.get(children.indexOf(currentNode) – 1);
CountDownLatch latch = new CountDownLatch(1);
zk.exists(lockPath + "/" + prevNode, event -> {
if (event.getType() == Event.EventType.NodeDeleted) {
latch.countDown();
}
});
latch.await(); // 阻塞等待
}
}
2. 核心优势
| 自动释放 | 客户端崩溃或网络断开,Session 超时后自动删除节点,无死锁 |
| 强一致性 | Zookeeper 保证顺序一致性,锁获取顺序严格有序 |
| 可重入 | 同一线程可多次获取锁(需记录线程标识) |
| 监听机制 | 非轮询等待,事件驱动唤醒,性能优于 Redis 的忙等 |
3. 局限性与优化
性能瓶颈:
- 写操作限制:Zookeeper 单节点写性能约 10K TPS,低于 Redis 的 100K+
- 羊群效应:锁释放时,所有等待客户端同时被唤醒,瞬间流量冲击
优化方案:
- 顺序监听:仅监听前一个节点,而非所有节点(已在上文实现)
- 本地缓存:Curator 框架缓存节点列表,减少 Zookeeper 读压力
三、数据库悲观锁:简单可靠的兜底方案
利用数据库的行级排他锁(如 MySQL SELECT … FOR UPDATE)实现分布式锁,适合低并发或已有数据库的场景。
1. 实现方式
方式一:唯一约束(Insert 锁):
— 锁表结构
CREATE TABLE distributed_lock (
lock_name VARCHAR(64) PRIMARY KEY,
lock_value VARCHAR(255),
expire_time TIMESTAMP,
UNIQUE KEY uk_lock_name (lock_name)
);
— 获取锁(插入成功即获得)
INSERT INTO distributed_lock (lock_name, lock_value, expire_time)
VALUES ('order_lock', 'uuid', NOW() + INTERVAL 30 SECOND);
— 释放锁
DELETE FROM distributed_lock WHERE lock_name = 'order_lock' AND lock_value = 'uuid';
方式二:行级锁(For Update):
— 预先插入锁记录
INSERT INTO distributed_lock (lock_name) VALUES ('order_lock');
— 获取锁(开启事务)
BEGIN;
SELECT * FROM distributed_lock
WHERE lock_name = 'order_lock'
FOR UPDATE; — 排他锁
— 执行业务逻辑…
COMMIT; — 提交后自动释放锁
Java 实现:
@Transactional
public boolean tryLock(String lockName, String value) {
try {
// 尝试获取锁,设置过期时间
int count = jdbcTemplate.update(
"UPDATE distributed_lock SET lock_value = ?, expire_time = DATE_ADD(NOW(), INTERVAL 30 SECOND) " +
"WHERE lock_name = ? AND (expire_time < NOW() OR lock_value IS NULL)",
value, lockName
);
return count > 0;
} catch (Exception e) {
return false;
}
}
2. 优缺点分析
| 优点 | 实现简单、无需引入新组件、易于理解 |
| 缺点 | 性能差(数据库连接成本高)、单点故障(数据库挂则锁失效)、死锁风险(事务未提交则锁不释放) |
| 适用场景 | 并发量 < 100 QPS、已有数据库基础设施、非核心链路 |
3. 生产优化
避免单点:
- 使用主从架构,但需注意主从延迟导致锁失效
- 或采用多数据库分片(如按 lock_name hash 到不同库)
死锁预防:
- 设置锁超时时间(如 30 秒),通过定时任务清理过期锁
- 业务逻辑必须包裹在事务中,确保锁及时释放
四、三种方案对比与选型
| 一致性 | 最终一致(存在争议) | 强一致(CP) | 强一致(但依赖 DB 可用性) |
| 性能 | 极高(10万+ QPS) | 中等(1万 QPS) | 低(<1000 QPS) |
| 可靠性 | 中(时钟漂移、GC 风险) | 高(自动释放、无死锁) | 中(DB 单点、连接池耗尽) |
| 实现复杂度 | 高(多节点运维) | 中(Curator 封装后简单) | 低(SQL 即可) |
| 自动释放 | 依赖过期时间(可能不准) | 会话断开自动释放 | 依赖事务提交 |
| 可重入 | 需自行实现 | Curator 支持 | 需自行实现 |
| 适用场景 | 高并发缓存、限流 | 分布式协调、Leader 选举 | 低频任务、已有 DB 场景 |
选型决策树
是否需要强一致性?
├── 是 → 并发量是否高?
│ ├── 高(>1万 QPS)→ Zookeeper(CP + 高性能平衡)
│ └── 低 → 数据库悲观锁(简单可靠)
└── 否 → 性能要求是否极高?
├── 是 → Redis RedLock(接受极低概率并发问题)
└── 否 → Redis 单节点 + Redisson(Lua 脚本保证原子性)
2025 年推荐实践
Redis 单节点 + Redisson(大部分场景):
- 使用 RLock 的看门狗机制自动续期
- Lua 脚本保证加锁 + 过期原子性
- 避免 RedLock 的复杂性,单 Redis 足够应对 99% 场景
Zookeeper + Curator(强一致场景):
- 分布式配置、Leader 选举、分布式队列
- 使用 InterProcessMutex 实现可重入锁
数据库(低频兜底):
- 定时任务、数据迁移等低频操作
- 结合 Quartz 或 XXL-Job 的分布式锁实现
五、总结
| Redis RedLock | 多节点多数派 | 时钟漂移、GC 停顿 | 慎用,优先用单节点 Redisson |
| Zookeeper | 临时顺序节点 + Watcher | 性能瓶颈、羊群效应 | 强一致场景首选,Curator 封装 |
| 数据库 | 行级锁 / 唯一约束 | 性能差、死锁 | 低频场景,必须加超时清理 |
终极建议:分布式锁没有银弹,Redis 追求性能,Zookeeper 追求一致,数据库追求简单。在高并发场景下,建议Redis 为主 + Zookeeper 兜底的双保险策略。
网硕互联帮助中心







评论前必须登录!
注册