7.3 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-08-09 12:00 |
Redis 分布式锁 — 测试题
概述
本测试覆盖 Redis 分布式锁的核心原理,包括 SETNX 原子性陷阱、Redlock 多实例容错、Redisson 看门狗续期、可重入锁 hash key 设计以及常见死锁场景分析。共 10 道题目(6 选择 + 3 填空 + 1 简答)。
一、选择题(6道,由浅入深)
难度阶梯: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
Q1(基础)— 考察定义层面
在 Redis 中尝试获取分布式锁时,以下哪段代码存在最严重的缺陷?
A. redis.set("lock", uuid, "NX")
B. redis.set("lock", uuid, "NX", "EX", 30)
C. redis.setnx("lock", uuid); redis.expire("lock", 30)
D. Lua 脚本:if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) end
Q2(基础)→
使用 SET NX EX 获取锁后,客户端在持有锁期间宕机了。当锁过期时间到达时,Redis 会自动删除该 key。但如果在业务执行完成之前锁就过期了,会出现什么问题?
A. Redis 会阻塞等待业务完成后再删除 key B. 其他客户端可能获取到同一把锁,导致数据竞争 C. 自动触发 Redlock 选举新的持有者 D. Lua 脚本检测到异常,自动延长锁有效期
Q3(进阶)— 核心原理
Redisson 的看门狗(Watch Dog)机制在什么条件下才会启动?
A. 每次调用 lock.lock() 时都会启动
B. 只在传入 leaseTime 参数时启动
C. 只有在加锁时不指定 leaseTime 参数时才启动
D. 看门狗需要手动通过 startWatchDog() 命令启用
Q4(进阶)— 比较/辨析
Redisson 实现可重入锁的数据结构是 Hash 类型,其 key 中存储的内容是什么?
A. client_id 和整数计数 B. threadID 和重入次数 C. UUID 和时间戳 D. hostname 和端口号
Q5(深入)— 场景推理
在一个包含 5 个独立 Redis 实例的 Redlock 部署中,客户端依次向所有 5 个实例发送 SET lock uuid NX EX 30。其中实例 R1、R3、R5 返回 OK,R2 返回 nil,R4 返回 OK。总耗时 T = 12ms。请问以下判断正确的是:
A. 加锁失败,因为只拿到了 3/5,未达到多数派 B. 加锁成功,有效 TTL 为 30s C. 加锁成功,有效 TTL 约为 19.988s D. 加锁成功,有效 TTL 为 20ms
Q6(深入)— 源码级/边界场景
关于解锁操作,为什么必须用 Lua 脚本保证"检查 value + 删除"的原子性?下面哪个场景最能说明不加原子性的危险?
A. 客户端 A 读取到 key 的 value 是自己的 clientId,但在执行 DEL 之前,锁已过期且客户端 B 重新获得了锁并设置了新的 value。此时 A 的 DEL 会把 B 的锁删掉。 B. Redis 主从切换时主节点未同步 DEL 命令导致从节点仍有锁 C. 客户端网络抖动导致 DEL 超时,锁永远不会被释放 D. SET NX 在高并发下存在多个客户端同时返回 OK 的情况
二、填空题(3道)
F1 — 填空1
Redlock 算法要求获得至少 _____ 个节点的 OK 响应才能判定加锁成功(假设部署了 5 个实例)。有效 TTL 的计算公式是:_____ − T(T 为总耗时)。
提示: 回忆 Redlock 的核心规则中关于 N/2+1 的部分。
F2 — 填空2
Redisson 看门狗的后台任务每 _____ 秒检查一次锁是否仍持有,每次续期到默认 _____ 秒。
提示: 原文中有一个表格列出了看门狗的行为细节。
F3 — 填空3
解锁 Lua 脚本的核心逻辑:只有当 redis.call("get", key) 返回的值等于传入的 _____ 时,才执行删除操作。这是为了防止误删其他客户端持有的锁。
提示: 考虑竞态场景中谁有资格删除这把锁。
三、简答题(1道)
S1
某金融系统需要在用户扣款接口上做防重复提交。面试官问你:"为什么不依赖 Redis 分布式锁来保证幂等,而要在数据库层面再加一层唯一约束+CAS?"请结合 Redis 分布式锁的局限性,给出你的完整回答思路。
答题框架提示:
- 从锁本身的可靠性局限谈起(clock drift、主从切换)
- 说明即使锁正确工作也无法替代的业务语义需求
- 提出"双重保障"架构建议
参考答案与解析
选择题答案
| 题号 | 正确答案 | 解析 |
|---|---|---|
| Q1 | A | A 选项只用了 SET NX 而没有设置过期时间(EX/PX),客户端宕机后锁永久不释放——死锁。B 是正确写法(原子 SET NX EX),C 虽然非原子但有 expire 兜底,D 是正确的解锁 Lua 脚本而非加锁。 |
| Q2 | B | 锁自动释放后,其他客户端可以获取到同一把锁,而此时原客户端仍在执行业务逻辑,两个客户端同时对同一资源做写操作,必然产生数据竞争。 |
| Q3 | C | 看门狗只在不指定 leaseTime 时启动。如果明确传入了 leaseTime,Redisson 认为你已经知道业务执行时间上限,无需续期。 |
| Q4 | B | Redisson 使用 Hash 存储 {threadID}: count,threadID 标识是哪个线程获得了锁,count 记录重入次数。unlock 时递减计数,归零才真正删除 key。 |
| Q5 | C | 5 个实例中获得 4 个 OK,4 >= 5/2+1=3,满足多数派所以加锁成功。有效 TTL = 30s - 0.012s ≈ 29.988s。 |
| Q6 | A | 这是经典的"误删他人锁"场景:没有 value 校验就直接 DEL,任何读到旧 value 的客户端都可以删除当前持有者的锁。Lua 脚本将 get + del 打包成原子操作是唯一解。 |
填空题答案
| 题号 | 答案 | 解析 |
|---|---|---|
| F1 | 3;30s(或"设定值") |
Redlock 要求 ≥ N/2+1 = 3 个实例 OK。有效 TTL = 设定过期时间 − 实际耗时,确保即使部分节点因网络延迟稍晚收到 DEL 也不会误判。 |
| F2 | 10;30 |
每 10 秒检查一次,续期到默认 30 秒。这意味着只要业务不超过 30 秒且不连续错过 3 次续期,锁就不会意外释放。 |
| F3 | clientId(或"客户端标识值") |
只有 value 匹配证明当前客户端确实是锁的持有者,才允许删除。否则可能是锁过期后被新客户端重新获取的场景。 |
简答题参考答案
S1:参考答案要点:
- 时钟漂移风险:NTP 同步异常可能导致系统时钟回拨,使得一个实例已释放锁而客户端仍持旧锁,Redlock 也不能完全规避此问题。
- 主从切换丢锁:Redis 单实例在主从切换时,主节点上的锁可能还未复制到从节点就宕机了,从节点成为新主后锁丢失。
- 业务语义不足:分布式锁只能保证互斥访问,不能保证"之前是否已经成功执行过"——如果第一次请求成功但网络超时导致客户端重试,锁无法区分这两次请求。
- 数据库兜底的不可替代性:唯一索引 + CAS 是最终防线,即使锁层面全部失效,数据库层面仍然能保证不会重复扣款。
- 最佳实践:应采用"锁 + 数据库二次校验"的双重保障策略,锁提升性能减少冲突,数据库保证最终一致性。
评分标准:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。