文章目录
-
- 先说结论
- 数据库锁:最朴素的方式
- Redis 锁:最常用的方式
- ZooKeeper 锁:最可靠的方式
- 三种方案怎么选
- 回答技巧与点评
-
- 加分回答
- 面试官点评
个人网站
单机锁(synchronized / ReentrantLock)只能锁同一个 JVM,分布式环境下多台机器怎么互斥?这就是分布式锁要解决的问题。面试官问这题,他想听的是:你能不能对比三种实现方案(数据库、Redis、ZooKeeper),说出各自的优缺点和适用场景?
先说结论
| 维度 | 数据库锁 | Redis 锁 | ZooKeeper 锁 || | ——|———-|———-|————–|| | 实现方式 | 唯一索引 / FOR UPDATE | SETNX + 过期时间 | 临时顺序节点 || | 性能 | 低 | 高 | 中 || | 可靠性 | 中(无过期机制) | 中(可能锁泄漏) | 高(临时节点自动释放) || | 公平性 | 非公平 | 非公平(可改造) | 公平(顺序节点) || | 自动释放 | 无(需手动删) | 有(过期时间) | 有(会话断开自动删) || | 适用场景 | 低频简单场景 | 高性能短任务 | 强一致性场景 ||
|一句话记住:数据库锁像"登记本"——简单但慢;Redis锁像"抢红包"——快但可能抢丢;ZK锁像"取号排队"——稳但稍慢"
数据库锁:最朴素的方式
方式一:唯一索引
— 加锁:插入一条记录,唯一索引保证互斥
INSERT INTO lock_table (lock_key, holder) VALUES ('order_123', 'node1');
— 解锁:删除记录
DELETE FROM lock_table WHERE lock_key = 'order_123';
方式二:悲观锁
— 加锁
SELECT * FROM lock_table WHERE lock_key = 'order_123' FOR UPDATE;
— 事务提交自动释放
COMMIT;
优点:实现简单,不需要额外组件。缺点:性能差(每次锁操作都要走数据库),没有过期机制(节点挂了锁就卡住了)。
Redis 锁:最常用的方式
基础版(SETNX):
// 加锁
Boolean locked = redis.setIfAbsent("lock:order_123", "node1", 30, TimeUnit.SECONDS);
// 解锁(Lua 脚本保证原子性)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else return 0 end";
redis.eval(script, "lock:order_123", "node1"); // 👈 防止删别人的锁
Redisson 分布式锁(生产推荐):
RLock lock = redisson.getLock("order_123");
lock.lock(); // 👈 内置看门狗续期
try {
// 业务逻辑
} finally {
lock.unlock();
}
Redisson 的"看门狗"机制解决了锁过期问题:默认 30 秒过期,但看门狗每 10 秒自动续期。如果持有锁的节点挂了,看门狗也停了,锁自然过期。
| 锁过期但业务没完 | 看门狗续期 |
| 误删别人的锁 | Lua 脚本校验 value |
| 不可重入 | Hash 结构记录重入次数 |
ZooKeeper 锁:最可靠的方式
临时顺序节点实现公平锁:
1. 创建锁节点 /locks/order-lock
2. 在锁节点下创建临时顺序子节点
/locks/order-lock/lock-0000000001 👈 序号最小,获得锁
/locks/order-lock/lock-0000000002 👈 Watch 前一个节点
/locks/order-lock/lock-0000000003 👈 Watch 前一个节点
3. 序号最小的获得锁
4. 释放锁 → 删除自己的节点 → 下一个节点收到 Watch 通知 → 获得锁
// Curator 分布式锁(生产推荐)
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order-lock");
lock.acquire();
try {
// 业务逻辑
} finally {
lock.release(); // 👈 临时节点自动删除,下一个节点被唤醒
}
ZK 锁的核心优势:
- 临时节点:持有锁的节点挂了,会话断开,节点自动删除 → 不会死锁
- 顺序节点:按创建顺序排队 → 公平锁,不会饿死
- Watch 通知:前一个释放后通知下一个 → 精准唤醒,不需要轮询
三种方案怎么选
| 场景 | 推荐 | 原因 || | ——|——|——|| | 高并发短任务 | Redis | 性能最高,毫秒级响应 || | 强一致性要求 | ZooKeeper | 不会死锁,公平可靠 || | 已有数据库不想加组件 | 数据库 | 零额外成本 || | 需要可重入 | Redis(Redisson) | 内置支持 || | 锁持有时间长 | ZooKeeper | 自动释放,不怕节点挂 ||
分布式锁全景
三种实现
├── 数据库 —— 唯一索引/FOR UPDATE,简单但慢
├── Redis —— SETNX+过期,快但需防死锁
└── ZooKeeper —— 临时顺序节点,稳但稍慢
Redis 关键问题
├── 锁过期 → 看门狗续期
├── 误删除 → Lua脚本校验
└── 不可重入 → Hash结构计数
ZK 核心优势
├── 临时节点 → 自动释放,不死锁
├── 顺序节点 → 公平锁
└── Watch → 精准唤醒
口诀:数据库锁最简单,性能差还怕死锁;
Redis锁快如风,看门狗防过期删;
ZK临时顺序节点,公平可靠不死锁;
高并发选Redis,强一致上ZK
回答技巧与点评
标准回答:分布式锁有三种实现:1)数据库锁——唯一索引或 FOR UPDATE,简单但性能差且无自动释放;2)Redis 锁——SETNX + 过期时间,性能高但需处理锁过期(看门狗续期)和误删除(Lua 脚本校验);3)ZooKeeper 锁——临时顺序节点,最可靠:临时节点保证不死锁,顺序节点保证公平,Watch 实现精准唤醒。选型:高并发选 Redis,强一致性选 ZK。
加分回答
面试官点评
这道题考的是你对 分布式互斥 方案的全面认识。最忌讳的回答是只说 Redis SETNX——面试官想听的是 三种方案的对比,特别是各自的坑(Redis 的锁过期和误删除、ZK 的公平性优势)。能讲清每个方案的问题和解决方案,再根据场景给推荐,就是高分回答。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪