在微服务架构或分布式系统中,Java 内置的锁(如 synchronized 或 ReentrantLock)只能限制当前 JVM 内部的线程互斥。当我们需要跨多个服务节点、跨进程地去抢占同一个资源(如“扣减库存”、“抢单”)时,就需要引入分布式锁。
本文将深入对比业界常见的三种分布式锁实现方案:MySQL、Redis 和 ZooKeeper,解析其原理、代码实现及优缺点。
MySQL 实现分布式锁主要利用了数据库的唯一性约束或排他锁(X锁)特性。
这是最直观的方法。我们创建一个锁表,并对 lock_key 字段加上唯一索引。
INSERT INTO lock_table (lock_key) VALUES ('order_101');。如果插入成功,则获取锁;如果报 Duplicate Key Error,则获取失败。
DELETE FROM lock_table WHERE lock_key = 'order_101';。
利用 MySQL 的事务机制,通过 FOR UPDATE 对某一行数据加排他锁。
START TRANSACTION;
-- 阻塞式等待获取锁
SELECT * FROM distributed_lock WHERE lock_key = 'order_101' FOR UPDATE;
-- 执行业务逻辑...
COMMIT; -- 提交事务即释放锁在使用 FOR UPDATE 时,WHERE 后面的字段(如 lock_key)必须有索引。
FOR UPDATE 的方案会长时间占用数据库连接。
这是目前互联网企业最常用的方案,核心优势是高性能。
早期我们使用 SETNX (Set if Not Exists) 指令,但在生产环境中,推荐使用 Redisson 框架,它封装了复杂的底层逻辑。
@Autowired
private RedissonClient redisson;
public void doBusiness() {
RLock lock = redisson.getLock("my-lock");
try {
// 尝试加锁,支持自动续期
lock.lock();
// 执行业务...
System.out.println("Processing...");
} finally {
// 释放锁前判断是否是当前线程持有
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}ZooKeeper (ZK) 保证 CP(强一致性),适用于对可靠性要求极高的场景(如金融核心系统)。通常配合 Curator 客户端使用。
利用 ZK 的临时顺序节点 (Ephemeral Sequential Node)。
/lock 目录下创建一个临时顺序节点。
维度 | MySQL | Redis (Redisson) | ZooKeeper |
|---|---|---|---|
底层机制 | 唯一索引 / 行锁 | SETNX / Lua 脚本 | 临时顺序节点 |
性能 | 低 (磁盘I/O) | 极高 (内存) | 中 (网络/磁盘) |
一致性 | 强 (依赖事务) | 弱 (主从切换可能丢锁) | 强 (CP模型) |
死锁风险 | 高 (需兜底策略) | 低 (看门狗机制) | 无 (断开即释放) |
复杂度 | 手动实现麻烦 | 框架封装好 | 框架封装好 |
选型建议:
作者提示:技术选型没有绝对的“最好”,只有“最适合”。在面试中回答此问题时,建议先说出 Redis 的方案,再对比 ZK 的强一致性优势,最后提一下 MySQL 的实现原理及坑点,展现你的知识广度。