vault backup: 2026-08-09 19:06:40

This commit is contained in:
2026-08-09 19:06:40 +08:00
parent e2975eb86b
commit 9d664545c9
46 changed files with 7287 additions and 0 deletions
@@ -0,0 +1,142 @@
---
tags: [test/review, architecture, arch/distributed, redis-distributed-lock]
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 分布式锁的局限性,给出你的完整回答思路。
> **答题框架提示**:
> 1. 从锁本身的可靠性局限谈起(clock drift、主从切换)
> 2. 说明即使锁正确工作也无法替代的业务语义需求
> 3. 提出"双重保障"架构建议
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| 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:**参考答案要点**:
1. **时钟漂移风险**:NTP 同步异常可能导致系统时钟回拨,使得一个实例已释放锁而客户端仍持旧锁,Redlock 也不能完全规避此问题。
2. **主从切换丢锁**:Redis 单实例在主从切换时,主节点上的锁可能还未复制到从节点就宕机了,从节点成为新主后锁丢失。
3. **业务语义不足**:分布式锁只能保证互斥访问,不能保证"之前是否已经成功执行过"——如果第一次请求成功但网络超时导致客户端重试,锁无法区分这两次请求。
4. **数据库兜底的不可替代性**:唯一索引 + CAS 是最终防线,即使锁层面全部失效,数据库层面仍然能保证不会重复扣款。
5. **最佳实践**:应采用"锁 + 数据库二次校验"的双重保障策略,锁提升性能减少冲突,数据库保证最终一致性。
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。
## 关联笔记
- [[ZooKeeper 分布式锁]]
- [[限流熔断降级]]