欢迎光临
我们一直在努力

孤舟笔记 分布式与微服务篇八 分布式锁到底怎么实现?三种方案对比,面试官想听的可不只是Redis

文章目录

    • 先说结论
    • 数据库锁:最朴素的方式
    • 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。

加分回答

  • RedLock 争议:Redis 作者提出 RedLock 算法(多节点过半加锁)来解决单点问题,但 Martin Kleppmann 质疑其在时钟跳变和 GC 停顿下的正确性。实际上生产环境更推荐单节点 Redis + Redisson 看门狗
  • 分布式锁的最佳实践:锁的粒度要细(锁订单 ID 而不是锁整个表)、持有时间要短(毫秒级而非秒级)、必须有降级方案(锁获取失败不能直接报错)
  • ETCD 锁:和 ZK 类似的协调服务,基于 Raft 协议,Go 生态常用。Kubernetes 就用 ETCD 做分布式锁和 Leader 选举
  • 面试官点评

    这道题考的是你对 分布式互斥 方案的全面认识。最忌讳的回答是只说 Redis SETNX——面试官想听的是 三种方案的对比,特别是各自的坑(Redis 的锁过期和误删除、ZK 的公平性优势)。能讲清每个方案的问题和解决方案,再根据场景给推荐,就是高分回答。

    原文阅读


    内容有帮助?点赞、收藏、关注三连!评论区等你 💪

    赞(0)
    未经允许不得转载:171主机测评 » 孤舟笔记 分布式与微服务篇八 分布式锁到底怎么实现?三种方案对比,面试官想听的可不只是Redis
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址