vault backup: 2026-08-09 19:06:40
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
---
|
||||
tags: [test/review, redis, rdb-aof, persistence, fork, snapshot]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# RDB与AOF持久化_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖 Redis 的 RDB 快照、AOF 追加日志以及混合持久化三种方案。共 10 道题:6 道选择题、3 道填空题、1 道简答题,考察对 fork 机制、COW 开销、重写原理及选型策略的理解。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
Redis 的 `BGSAVE` 命令触发 RDB 持久化的核心步骤中,哪一步是造成内存峰值的关键因素?
|
||||
|
||||
A. 子进程遍历内存数据结构写入临时文件
|
||||
B. fork 子进程时操作系统复制被修改的内存页(Copy-On-Write)
|
||||
C. 将临时文件 mv 替换为 dump.rdb
|
||||
D. 主进程继续处理客户端写请求
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
在 AOF 的三种 `appendfsync` 策略中,哪一种会在每次写命令执行后都调用一次操作系统 fsync 系统调用?
|
||||
|
||||
A. always
|
||||
B. everysec
|
||||
C. no
|
||||
D. 自动触发模式
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
RDB 文件采用压缩二进制格式存储。关于其特性,以下说法正确的是:
|
||||
|
||||
A. RDB 文件是纯文本格式,可以直接用 text editor 查看和编辑
|
||||
B. RDB 文件在不同 Redis 版本之间完全兼容,可以跨版本还原
|
||||
C. RDB 是无损压缩的二进制格式,但不同版本间不兼容
|
||||
D. RDB 只保存过期 key 的数据,跳过已失效的数据
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
AOF 重写(Rewrite)过程中,子进程序列化数据的方式是:
|
||||
|
||||
A. 读取旧的 AOF 文件,去除重复命令后写入新文件
|
||||
B. 遍历当前内存数据,将每个 key 用 SET 命令逐条序列化
|
||||
C. 读取 RDB 快照文件并转换为 AOF 格式
|
||||
D. 直接从数据库查询最新数据然后序列化
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
某 Redis 实例内存使用量约为 12GB,每次 BGSAVE 时 COW 导致额外占用约 500MB,持续数秒。运维同学希望在不停机的情况下降低 BGSAVE 期间的内存峰值。以下哪种方案最有效?
|
||||
|
||||
A. 增大 save 时间窗口减少触发频率
|
||||
B. 改用 `appendfsync no` 策略关闭持久化
|
||||
C. 开启混合持久化并将业务拆分到多个实例
|
||||
D. 增加 maxmemory 配置以提供更大的内存池
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
混合持久化(Hybrid Persistence)的文件结构是怎样的?
|
||||
|
||||
A. 前半部分是 AOF 文本指令,后半部分是 RDB 二进制快照
|
||||
B. 前半部分是 RDB 全量快照,后半部分是 fork 时刻之后的增量 AOF 命令
|
||||
C. RDB 和 AOF 完全独立,分别存储在两个文件中
|
||||
D. 随机分布,RDB 部分和 AOF 部分交错排列
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
`save 900 1` 的含义是:在 900 秒内有至少 ______ 个 key 被修改,则自动触发 BGSAVE 快照。
|
||||
|
||||
> **提示**: 语法为 save <seconds> <changes>。
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
AOF 重写期间,父进程同时将新的写命令写入一个叫 ______ 的缓冲区,确保重写完成后的数据不会丢失。
|
||||
|
||||
> **提示**: 这个缓冲区的名字与其用途直接相关。
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
当 Redis 内存超过 ______ GB 时,fork 导致的 COW 成本会显著剧增,需要特别注意性能影响。
|
||||
|
||||
> **提示**: 这是一个经验阈值。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
一个电商系统的购物车数据存储在 Redis 中,数据特点如下:
|
||||
- 数据量中等(内存约 3GB),读多写少
|
||||
- 对数据安全性要求较高,最多允许丢失 1 分钟数据
|
||||
- 每晚凌晨需要对数据进行冷备份到对象存储
|
||||
|
||||
请为该系统设计一套持久化方案,说明具体配置选择及其理由,并解释每层设计如何满足不同需求。
|
||||
|
||||
> **答题框架提示**:
|
||||
> 1. 日常运行的持久化策略选择(AOF vs RDB vs 混合)
|
||||
> 2. appendfsync 的参数选择依据
|
||||
> 3. 冷备份的操作方式及注意事项
|
||||
> 4. 各方案的取舍权衡
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | B | fork 瞬间父子进程共享内存页,当父进程修改某页时操作系统会复制该页给子进程使用(Copy-On-Write)。这意味着 RDB 过程中的内存峰值 = 当前内存 + fork 瞬间的增量变化量。其他选项虽然是流程的一部分但不是造成峰值的原因。 |
|
||||
| Q2 | A | always 策略每条命令执行完就 fsync,等同于 MySQL innodb_flush_log_at_trx_commit=1,最安全但性能最差。everysec 每秒一次,no 由 OS 决定刷新时机。 |
|
||||
| Q3 | C | RDB 是无损压缩的二进制格式,空间效率高,但不同 Redis 版本间的 RDB 文件不兼容,不能跨版本还原。 |
|
||||
| Q4 | B | AOF 重写不是简单地删旧写新,而是 fork 子进程后直接遍历当前内存数据,将每个 key 用 SET 命令序列化。这比读取旧 AOF 更高效,因为去除了已过期 key 的记录。 |
|
||||
| Q5 | C | 12GB 已经超过了 10GB 的经验阈值,fork 的 COW 成本会非常可观。混合持久化可以加快启动恢复速度,拆分实例可以避免单次 fork 过大的开销。A 只能减少触发频率但不能降低单次成本;B 会牺牲数据安全;D 治标不治本。 |
|
||||
| Q6 | B | 混合持久化 = RDB 全量快照(前半部分)+ 增量 AOF 命令(后半部分)。RDB 部分保证从 fork 时刻的完整状态,AOF 部分保证增量不丢,兼顾了启动速度和数据安全性。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | 1 | save 900 1 表示 900 秒内至少有 1 个 key 变更就触发快照。这是 Redis 默认的自动触发规则之一(通常搭配 save 300 10 和 save 60 10000 形成三级触发体系)。 |
|
||||
| F2 | aof_rewrite_buffer | AOF 重写期间旧 AOF 保持不变,子进程生成新文件的同时父进程把新写命令累积到 aof_rewrite_buffer 中,合并后原子替换原 AOF 文件,确保零丢失。 |
|
||||
| F3 | 10 | 当内存超过 10GB 时,fork 导致 COW 成本剧增。此时应考虑增大 AOF 重写阈值或将业务拆分到多实例,避免单次 fork 过大的内存开销。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. 日常运行推荐开启混合持久化(aof-use-rdb-preamble yes),兼顾数据安全性和启动恢复速度。大多数互联网公司的标准配置。
|
||||
2. appendfsync 设置为 everysec(默认值),在性能和安全性之间取得平衡——最多丢 1 秒数据,系统调用开销仅约 1ms。
|
||||
3. 冷备策略:每天凌晨通过 BGSAVE 生成 dump.rdb 后上传到 OSS/S3。注意 SAVE 是阻塞命令,不要在高峰期执行,应使用 BGSAVE 后台执行后再复制文件。
|
||||
4. 对于 3GB 数据量,fork COW 成本可控,无需拆分实例。但可以监控 last_bgsave_status 确认健康度。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点及各方案的取舍分析。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/core/Redis 五大核心数据结构]]
|
||||
- [[03.Redis/core/集群与哨兵机制]]
|
||||
- [[03.Redis/strategies/多级缓存架构设计]]
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
tags: [test/review, redis, data-structures, sds, quicklist, skiplist]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# Redis 五大核心数据结构_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖 Redis 五种核心数据结构的底层实现原理,包括 SDS、quicklist、hash编码切换、intset/hashtable 切换以及 skiplist + hashtable 的 ZSet 结构。共 10 道题:6 道选择题、3 道填空题、1 道简答题。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
Redis String 类型底层使用 SDS(Simple Dynamic String)而非 C 字符串。以下哪一项**不是** SDS 相比 C 字符串的核心优势?
|
||||
|
||||
A. O(1) 获取字符串长度
|
||||
B. 二进制安全,不会因为 \0 截断
|
||||
C. 支持自动缩容以释放多余内存
|
||||
D. 修改字符串时通过 free 字段实现惰性空间释放
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
对于一个 Hash 类型的键,在 Redis 4.0+ 中,当满足什么条件时会从 ziplist 切换到 hashtable 编码?
|
||||
|
||||
A. 元素数量超过 512 或者任意 value 长度超过 64 字节
|
||||
B. 元素数量超过 1000
|
||||
C. 任意 key 长度超过 64 字节
|
||||
D. Hash 中包含浮点数类型的 value
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
Redis List 从 3.2 版本起统一使用 quicklist 实现。关于 quicklist 的结构特点,以下说法正确的是:
|
||||
|
||||
A. quicklist 是一个双向链表,每个节点本身是一个独立的 linkedlist
|
||||
B. quicklist 的每个节点是 ziplist(或 listpack),通过 list-max-ziplist-size 控制节点大小
|
||||
C. quicklist 只能单向遍历,无法支持 LPUSH 和 RPUSH 的组合操作
|
||||
D. quicklist 的节点大小固定为 4KB,不支持动态调整
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
关于 Set 类型的 intset 和 hashtable 两种编码,以下哪项描述是正确的?
|
||||
|
||||
A. intset 可以存储字符串,但 hashtable 只能存储整数
|
||||
B. intset 升级到达到的最大整数格式为 int64_t,且不可降级
|
||||
C. 当 Set 中包含混合类型(整数 + 字符串)时,Redis 会同时使用 intset 和 hashtable
|
||||
D. intset 查找时间复杂度为 O(N),hashtable 平均为 O(1)
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
某电商系统使用 ZSet 实现商品销量排行榜,zadd score member 其中 score 为销量数值。随着商品数量从几十增长到数百万,ZSet 内部编码发生了什么变化?这种变化的根本原因是什么?
|
||||
|
||||
A. 从 hashtable 切换到 skiplist,因为 hashtable 不够灵活
|
||||
B. 从 quicklist 切换到 ziplist,因为数据量增大后 ziplist 更高效
|
||||
C. 从 quicklist 切换到 skiplist + hashtable,因为 skiplist 提供 O(log N) 的有序访问而 hashtable 提供 O(1) 的成员查找
|
||||
D. 编码保持不变,始终使用 skiplist + hashtable,因为 Redis 会自动优化
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
Redis 7.0 引入了一项针对小字符串的内存优化:SDS_TYPE_5 直接存储在 dictEntry 的键槽中。这项优化的主要目的是什么?
|
||||
|
||||
A. 减少字符串比较的时间复杂度
|
||||
B. 完全避免额外堆分配,降低内存碎片和 malloc 开销
|
||||
C. 使短字符串可以使用更大的 maxlevel=64 来提升跳表性能
|
||||
D. 让 SDS 兼容 C 字符串协议以便与旧版客户端通信
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
Redis ZSet 的内部实现组合了两种数据结构:跳表用于按 score 排序,______ 用于按 member 做 O(1) 查找。
|
||||
|
||||
> **提示**: 思考 ZSet 需要同时支持"按分数范围查询"和"精确成员查询"两个维度。
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
对于 quicklist,如果将 `list-max-ziplist-size` 设置为 **-2**,则表示每节点最大不超过 ______ KB。
|
||||
|
||||
> **提示**: 回顾表格中负数取值的含义。
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
Hash 类型在 Redis 4.0 之前使用 zipmap,4.0~5.x 使用 ziplist。默认切换到 hashtable 的条件是:元素数量大于 512 或任意 value 长度超过 ______ 字节。
|
||||
|
||||
> **提示**: 这是 Redis 对压缩列表上限的一个关键阈值。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
一个社交应用的点赞功能需要同时满足以下需求:
|
||||
1. 统计每个用户的点赞总数(类似 `user_id:like_count`)
|
||||
2. 维护一个用户点赞过的所有帖子 ID 列表(类似 `user:1:liked_posts`)
|
||||
3. 对帖子维护一个被点赞用户列表,并按点赞时间排序
|
||||
|
||||
请结合 Redis 五大核心数据结构的特点,说明你会如何选择数据结构来存储以上信息?为什么不用其他替代方案?
|
||||
|
||||
> **答题框架提示**:
|
||||
> 1. 逐一对应三个业务需求选择最合适的结构
|
||||
> 2. 考虑每种结构的编码切换策略
|
||||
> 3. 分析替代方案的缺陷(如用 List 维护排序的代价)
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | C | SDS 不支持自动缩容,只通过 free 字段保留空闲空间供下次复用。这是设计取舍——频繁缩容反而浪费 CPU。其他选项均为 SDS 核心优势:len 字段 O(1) 获得长度、记录 len 而非依赖 \0 实现二进制安全、free 实现惰性空间释放。 |
|
||||
| Q2 | A | Redis 4.0+ 的 Hash 哈希表切换条件是:有一个 value > 64 字节或元素数 > 512。注意 Redis 4.0 已将 ziplist 作为 Hash 编码废弃,默认用 hashtable。 |
|
||||
| Q3 | B | quicklist 是双向链表,每节点为 ziplist(Redis 7.0+ 为 listpack)。通过 list-max-ziplist-size 控制每节点规模。A 错在节点是 ziplist 而非 linkedlist;C 错在支持两端操作;D 错在可配置。 |
|
||||
| Q4 | B | intset 升级路径是 int16_t → int32_t → int64_t,一旦扩容 realloc 整个结构且不可降级。A 反了;C 不存在混存机制,非整数出现就全部转为 hashtable;D 反了,intset 二分查找 O(log N),hashtable 平均 O(1)。 |
|
||||
| Q5 | C | 数据量大后 ZSet 从 ziplist/quicklist 切换到 skiplist + hashtable 双结构。skiplist 保证有序性(O(log N) 查找),hashtable 保证 O(1) 的 member 查找。D 不对,小数据量时用 quicklist/ziplist 省内存。 |
|
||||
| Q6 | B | Redis 7.0 的 native 优化中,小字符串用 SDS_TYPE_5 直接内嵌在 dictEntry 中,无需额外 malloc 分配独立对象。这减少了内存碎片并降低了 malloc 系统调用次数。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | hashtable | ZSet 同时维护了一个 skiplist(按 score 升序排列)和一个 hashtable(按 member 做 O(1) 查找),两者同步更新,兼顾有序性和快速定位。 |
|
||||
| F2 | 8 | list-max-ziplist-size = -1 表示不限制(仅受 memory 约束),-2 <= 8KB,-3 <= 4KB,-4 <= 2KB,-5 <= 1KB(默认)。负数表示以 KB 为单位的大小上限。 |
|
||||
| F3 | 64 | 这是 Hash 编码切换的关键阈值之一:value 长度 > 64 字节 或元素数 > 512,都会促使 Redis 从 ziplist/zipmap 切换到 hashtable。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. 点赞总数使用 String 类型,通过 INCR 命令原子递增,简单高效;也可嵌套在 Hash 中用 field 存储多种计数。
|
||||
2. 点赞帖子列表可用 ZSet,score 存点赞时间戳,member 存 post_id,天然有序且支持范围查询(ZREVRANGEBYSCORE)。
|
||||
3. 帖子的点赞用户列表同样用 ZSet,score 为点赞时间,member 为用户 ID,配合 ZRANGE 取 Top-N。
|
||||
4. 替代分析:List 虽然能存列表,但不支持按时间排序的高效插入;Hash 适合字段-值映射但不适合有序列表场景。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点,并给出合理的替代方案分析。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/core/RDB 与 AOF 持久化]]
|
||||
- [[03.Redis/core/集群与哨兵机制]]
|
||||
- [[03.Redis/strategies/多级缓存架构设计]]
|
||||
- [[03.Redis/strategies/缓存穿透击穿雪崩解决方案]]
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
tags: [test/review, redis, cluster-sentinel, sharding, gossip, failover]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# 集群与哨兵机制_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖 Redis Sentinel 的高可用故障转移机制和 Redis Cluster 的槽位分片架构。共 10 道题:6 道选择题、3 道填空题、1 道简答题,考察对 CAP 权衡、Gossip 协议、选举流程和槽位迁移的理解。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
Redis Sentinel 判定 master 节点进入"客观下线"(ODOWN)状态的最低条件是:
|
||||
|
||||
A. 单个 Sentinel 节点 PING 超时
|
||||
B. quorum(n/2+1)个 Sentinel 认为 master SDOWN
|
||||
C. 所有 Sentinel 节点一致同意
|
||||
D. master 连续 5 次 PING 无响应
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
关于 Redis Cluster 的哈希槽分配,以下哪项计算方式是正确的?
|
||||
|
||||
A. MD5(key) % 16384
|
||||
B. CRC16(key) % 16384
|
||||
C. SHA1(key) % 16384
|
||||
D. Hash(key) % 1024
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
Sentinel 进行故障转移时,选择 slave 升为主节点的优先依据是:
|
||||
|
||||
A. priority 参数最低的那个 slave
|
||||
B. replication offset 最大的那个 slave
|
||||
C. 运行时间最长的 slave
|
||||
D. 最近收到 PING 回复最快的 slave
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
Redis Sentinel 和 Redis Cluster 在设计上做出了不同的取舍,以下对比错误的是:
|
||||
|
||||
A. Sentinel 支持 DB0~DB15 多库,Cluster 仅支持 DB0
|
||||
B. Sentinel 不支持水平分片,Cluster 通过 16384 槽位分片实现扩展
|
||||
C. Sentinel 倾向 AP 可用性优先,Cluster 倾向 CP 一致性优先
|
||||
D. Sentinel 扩容需要手动迁移或重新搭建,Cluster 可在线增量迁移
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
某服务部署了 3 个 Sentinel 节点来保护一个 Master-Slave 集群。如果其中 1 个 Sentinel 因网络隔离不可达,会发生什么?
|
||||
|
||||
A. 剩余的 2 个 Sentinel 仍然能够达成 quorum(n/2+1=2),继续执行故障检测和转移
|
||||
B. 由于无法达到 quorum,故障检测完全停止
|
||||
C. 剩余的 Sentinel 会自动新增第 4 个 Sentinel 来补足票数
|
||||
D. Master 进入只读模式等待失联的 Sentinel 恢复
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
Redis Cluster 在执行在线槽位迁移时,源节点和目标节点通过什么标志位协作完成过渡?
|
||||
|
||||
A. MIGRATING 和 IMPORTING
|
||||
B. TRANSFER 和 RECEIVE
|
||||
C. SOURCE 和 TARGET
|
||||
D. PREPARING 和 COMMITTING
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
Sentinel 故障转移的第一步是将 master 标记为 SDOWN(主观下线),需要至少 ______ 个 Sentinel 确认才会升级为 ODOWN(客观下线)。
|
||||
|
||||
> **提示**: 如果有 3 个 Sentinel,quorum = n/2 + 1 = 3/2 + 1 = ?
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
Redis Cluster 将数据划分为 ______ 个 hash slot,Key 的定位通过 CRC16(key) % 16384 计算得到对应的槽位。
|
||||
|
||||
> **提示**: 这个数字是一个质数附近的数值。
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
当客户端向错误的 Cluster 节点发送请求时,RabbitMQ(应为 Redis Cluster)会返回 ______ 重定向消息,告知客户端前往正确的节点查找。
|
||||
|
||||
> **提示**: 有两种重定向类型,ASK 用于迁移中的过渡期。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
你正在为一个需要高可用的金融交易系统设计 Redis 基础设施。该系统的读写比例约为 3:7,对数据一致性要求极高但不支持多 DB(即只用 DB0)。已知有以下约束条件:
|
||||
- 不能容忍脑裂现象导致的双主问题
|
||||
- 需要在故障发生时自动完成故障转移
|
||||
- 客户端连接地址需要保持相对稳定
|
||||
|
||||
请选择合适的架构方案(Sentinel 或 Cluster),列出你的配置建议,并说明如何将脑裂风险降到最低。
|
||||
|
||||
> **答题框架提示**:
|
||||
> 1. 架构方案的选择理由(CP vs AP 权衡)
|
||||
> 2. Sentinel 的部署数量和位置
|
||||
> 3. 关键的参数配置
|
||||
> 4. 脑裂防护机制(min-replicas-to-write 等)
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | B | quorum = n/2 + 1,其中 n 是配置的 Sentinel 总数。3 个 Sentinel 时需要 2 个认为 master SDOWN 才会触发 ODOWN。A 只是单个节点的 SDOWN(主观下线)。C 过于严格不利于故障快速发现。D 不是判定标准。 |
|
||||
| Q2 | B | Redis Cluster 使用 CRC16(key) % 16384 计算 Key 所属的槽位。MD5/SHA1 不是 Cluster 使用的算法。 |
|
||||
| Q3 | B | Sentinel 选择 replication offset 最大的 slave 升级为主,因为这代表它与 master 同步的最新、最完整的数据。priority 可作为辅助排序条件。 |
|
||||
| Q4 | C | 反了!Sentinel 倾向 CP(主从切换期间短暂不可用但保证一致),Cluster 倾向 AP(分片后允许不同分区有不同视角)。A、B、D 均为正确描述。 |
|
||||
| Q5 | A | 3 个 Sentinel 的 quorum = 3/2 + 1 = 2。剩余 2 个可达的 Sentinel 刚好达到 quorum,可以继续执行故障检测。少数派宕机不影响多数派的正常工作。 |
|
||||
| Q6 | A | 迁移时源节点 setslot MIGRATING <slot> <target-id>,目标节点 setslot IMPORTING <slot> <source-id>。迁移完成后双方取消标志位设为 NODE。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | 2 | 3 个 Sentinel 的 quorum = 3/2 + 1 = 2。只要 2 个及以上 Sentinel 认为 master SDOWN,就会将其标记为 ODOWN 并触发后续故障转移流程。 |
|
||||
| F2 | 16384 | Redis Cluster 固定使用 16384 个槽位(0-16383),Key 通过 CRC16(key) % 16384 分配到对应槽位所在的节点。 |
|
||||
| F3 | MOVED / ASK | 向错误节点发请求时,若 slot 已迁移到目标节点,目标节点返回 MOVED(迁移完成)或 ASK(迁移中过渡)重定向,让客户端重试正确节点。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. 选择 Redis Sentinel 而非 Cluster。原因:数据一致性要求高(CP 倾向)、读写比 3:7 且不需要水平扩展、只用单 DB(Cluster 仅支持 DB0 但这里正好满足)。
|
||||
2. Sentinel 部署至少 3 个奇数节点,跨机房/机架部署避免单点故障。quorum 设为 n/2 + 1。
|
||||
3. 关键参数:down-after-milliseconds 合理设置(如 30000ms)、failover-timeout 控制超时、parallel-syncs 控制同步并发数。
|
||||
4. 脑裂防护:设置 min-replicas-to-write(如 ≥1)和 min-replicas-max-lag(如 10s)。当 master 连接的合法从节点数低于阈值或延迟超过限制时,master 拒绝写操作,防止脑裂时双主写入。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖架构选择理由、部署方案、关键参数和脑裂防护四个维度。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/core/Redis 五大核心数据结构]]
|
||||
- [[03.Redis/core/RDB 与 AOF 持久化]]
|
||||
- [[03.Redis/strategies/多级缓存架构设计]]
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
tags: [test/review, redis, heavykeeper, hot-key-detection, top-k, count-min-sketch]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# HeavyKeeper热点探测算法_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖 HeavyKeeper 作为在线 Top-K 热点检测算法的核心原理,包括 Fading Count 概率衰减、双计数器结构、最小堆淘汰机制及与其他方案的对比。共 10 道题:6 道选择题、3 道填空题、1 道简答题。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
HeavyKeeper 是一个什么样的算法?
|
||||
|
||||
A. 离线批量排序算法,需要对全量数据排序后取 Top-K
|
||||
B. 在线流式计数算法,以 O(K) 空间复杂度实时维护访问量最高的 K 个 key
|
||||
C. 基于规则的热点阈值告警系统
|
||||
D. 用于 Redis 集群分片的负载均衡算法
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
HeavyKeeper 的 Fading Count 衰减公式为:`new_count = old_count * (1 - alpha) + alpha`。当 alpha 取值较大(接近 1)时,会发生什么?
|
||||
|
||||
A. 计数器衰减极慢,能长期记住热点
|
||||
B. 计数器衰减快,对突发热点更敏感但不稳定
|
||||
C. 计数器不再衰减,变成固定计数
|
||||
D. 计数器变成负数
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
HeavyKeeper 采用双计数器结构(Main Counter + Error Counter),与 Count-Min Sketch 相比的关键优势是什么?
|
||||
|
||||
A. HeavyKeeper 使用了更多的 hash 函数
|
||||
B. Error Counter 可以估计碰撞干扰并从 Main Counter 中减去,大幅降低误报率
|
||||
C. HeavyKeeper 不需要最小堆来维护 Top-K
|
||||
D. Error Counter 比 Main Counter 有更高的精度
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
在空间复杂度方面,HeavyKeeper 的典型配置是 m=4, K=1000(每个计数器 uint32,4 bytes),总空间约为:
|
||||
|
||||
A. 4KB
|
||||
B. 16KB
|
||||
C. 64KB
|
||||
D. 256KB
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
在一个高并发的电商秒杀场景中,HeavyKeeper 部署在每个应用服务端实例上监控各 SKU 的访问热度。当发现某个 SKU 进入 Top-K 时,自动触发的优化措施是:
|
||||
|
||||
A. 将该 SKU 的所有请求转发到单一的 Redis 节点处理
|
||||
B. 触发本地缓存预热或多实例分片隔离
|
||||
C. 暂停对该 SKU 的所有写操作
|
||||
D. 将所有请求排队等待统一处理
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
HeavyKeeper 的 estimateFrequency 方法在查询某个 key 的真实频率时,是如何利用两个计数器表的?
|
||||
|
||||
A. 取 m 个表中 Main Counter 的最大值加上 Error Counter 的最小值
|
||||
B. 取 m 个表中 Main Counter 的最小值减去 Error Counter 的最大值
|
||||
C. 取 m 个表中 Error Counter 的最小值减去 Main Counter 的最大值
|
||||
D. 对所有 m 个表的两个计数器求和取平均
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
HeavyKeeper 内部维护一个大小为 K 的 ______ 来保留当前访问量最高的 K 个 key。只有新元素的估计频率大于堆顶时才有机会进入 Top-K。
|
||||
|
||||
> **提示**: 堆的顶部是最小还是最大的元素?
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
HeavyKeeper 的空间复杂度为 O(m × K),其中 m 是计数表的行数(hash 函数数,通常取 ______ ~ 8),K 是要维护的 Top-K 大小。
|
||||
|
||||
> **提示**: m 的典型下限值是多少?
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
生产环境中 HeavyKeeper 的 alpha 参数一般取 ______ ~ 0.05,alpha 越大衰减越快、响应突发热点但不稳定;alpha 小则衰减慢、能记住长期热点但对突发反应迟钝。
|
||||
|
||||
> **提示**: 回顾文档中的调优经验。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
一个视频网站的 CDN 调度系统需要实时监控全球边缘节点的资源访问热度。需求如下:
|
||||
- 每秒约有 1 亿次访问请求
|
||||
- 需要找出 Top-1000 的热度内容
|
||||
- 需要能快速感知新冒出的热点(应对突发事件如热门视频发布)
|
||||
- 不能承受过高的内存开销
|
||||
|
||||
请设计一个基于 HeavyKeeper 的热点检测方案,说明:
|
||||
1. 部署架构(在哪里部署 HeavyKeeper 实例?)
|
||||
2. Alpha 参数的选择依据
|
||||
3. 为什么选择 HeavyKeeper 而非 Count-Min Sketch?
|
||||
|
||||
> **答题框架提示**:
|
||||
1. 部署位置和聚合策略
|
||||
2. Alpha 参数对突发敏感度的影响
|
||||
3. HeavyKeeper vs CMS 的核心差异
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | B | HeavyKeeper 是美团开源的在线 Top-K 热点检测算法,能够在 O(K) 空间复杂度的前提下以极低计算开销实时维护访问量最高的 K 个 key。它具有抗误报、自适应衰减的能力。A 错误——它是流式在线算法而非离线批处理。 |
|
||||
| Q2 | B | alpha 越大衰减越快,对突发热点更敏感但也更不稳定。alpha 小则衰减慢能记住长期热点但对突发反应迟钝。这是一个需要权衡的参数。 |
|
||||
| Q3 | B | HeavyKeeper 的双计数器结构中,Error Counter 专门用来估计其他冲突 key 可能造成的虚假抬高值。真实频率 ≈ Main Counter 最小值 - Error Counter 最大值。这是相比 CMS 的关键改进——CMS 没有误差补偿,所有 collision 都被计入。 |
|
||||
| Q4 | B | m=4, K=1000 → 4 × 1000 = 4000 个计数器 × 4 bytes = 16KB。极其紧凑!这也是 HeavyKeeper 工程落地的最大优势之一。 |
|
||||
| Q5 | B | 发现热点 SKU 后自动触发本地缓存预热(L1)或多实例分片隔离,让不同用户的请求打散到多个 Redis 实例,避免单个实例被打垮。 |
|
||||
| Q6 | B | estimateFrequency 的核心:取 m 个表中 Main Counter 的最小值(与 CMS 一致),减去 Error Counter 的最大值(HeavyKeeper 的独有贡献),得到估计的真实频率。如果结果小于 0 则返回 0。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | 最小堆(min-heap) | 最小堆的堆顶是当前 Top-K 中最小的元素。新元素只有大于堆顶时才可能进入 Top-K,否则忽略。弹出堆顶插入新元素维持堆大小不变。 |
|
||||
| F2 | 4 | m 是计数表行数(hash 函数数),通常取 4~8。更多行能提高精度但增加内存和计算开销。典型配置 K=1000, m=4 仅需 16KB。 |
|
||||
| F3 | 0.01 | 生产环境中 alpha 一般取 0.01~0.05,根据业务流量特征实验确定。大 alpha 对突发敏感但容易抖动,小 alpha 稳定但对突发反应迟钝。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. 部署架构:在每个应用服务端实例上部署 HeavyKeeper 实例统计 local key 的 QPS,然后定期(如每秒)将所有实例的局部 Top-K 合并到全局 Top-K。或者在网关层统一部署一个 HeavyKeeper 收集所有流量的完整视图(更精确但单点压力大)。
|
||||
2. Alpha 参数选择:取 0.03~0.05 偏大值。原因:视频网站热点可能突然爆发(如新剧上线),需要快速感知突发热点。较小的 alpha 虽然稳定但响应延迟偏高。
|
||||
3. HeavyKeeper vs CMS 的核心差异:CMS 只有上界没有下界,所有 collision 都被计入 Main Counter 导致误报率高;HeavyKeeper 通过 Error Counter 减去估计的碰撞干扰,大幅降低误报率。此外 HeavyKeeper 原生支持 Top-K(通过最小堆),而 CMS 需要外部维护。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖部署架构、alpha 选择依据和算法对比三个维度。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/strategies/多级缓存架构设计]]
|
||||
- [[03.Redis/strategies/缓存穿透击穿雪崩解决方案]]
|
||||
- [[03.Redis/core/集群与哨兵机制]]
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
tags: [test/review, redis, multi-level-cache, caffeine, cache-consistency, avalanche]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# 多级缓存架构设计_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖 L1/Caffeine 本地缓存、L2/Redis 远程缓存和 L3/MySQL 持久层组成的三级缓存架构。共 10 道题:6 道选择题、3 道填空题、1 道简答题,考察一致性策略、雪崩防护和 capacity planning 的计算能力。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
在三级缓存架构(Caffeine → Redis → MySQL)中,L1 缓存最主要的局限性是什么?
|
||||
|
||||
A. 读写延迟高于 L2 缓存
|
||||
B. 数据无法在多实例间同步,每个实例有自己的缓存视图
|
||||
C. 不支持任何淘汰算法
|
||||
D. 只能通过配置文件一次性设置,运行期不可调整
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
关于二级缓存的一致性更新策略,以下哪种做法**最不推荐**?
|
||||
|
||||
A. 先更新 DB 再删除缓存
|
||||
B. 先删除缓存再更新 DB
|
||||
C. 先删缓存再写 DB——会导致写操作完成后、DB 写入前的窗口期内读请求拿到陈旧缓存
|
||||
D. 使用消息队列广播各实例清除自己的 L1 缓存
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
在多级缓存中,使用 singleflight 来防止"缓存击穿"的核心机制是:
|
||||
|
||||
A. 在 L1 层互斥锁保证只有一个线程能读 L2
|
||||
B. 对同一个 key 的并发查询只允许一个线程执行底层查找函数,其余线程共享结果
|
||||
C. 在 L2 Redis 层设置分布式锁防止同时写入
|
||||
D. 自动将所有并发请求合并为一次批量查询
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
以下哪组缓存失效传播方案各有不同的适用场景?
|
||||
|
||||
A. 短 TTL + 异步刷新适用于实时性要求极高的场景;MQ 广播适用于大规模部署
|
||||
B. MQ 广播实时性好但增加系统复杂度;Canal 监听 binlog 解耦彻底适合大规模部署
|
||||
C. 定时扫描比对成本最低且最可靠;Canal 监听只适用于 MySQL
|
||||
D. 三种方案性能完全相同,区别仅在于实现语言
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
某电商商品详情页使用多级缓存。单机 QPS 约为 10000,L1 hit rate = 90%,L2 hit rate(相对 L1 miss)= 80%。请问最终打到外部 Redis 的请求大约是多少 QPS?
|
||||
|
||||
A. 约 8000 QPS
|
||||
B. 约 2000 QPS
|
||||
C. 约 200 QPS
|
||||
D. 约 20 QPS
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
在 singleflight 实现的缓存获取流程中,如果 L1 miss 且 L2 也 miss,singleflight 的作用范围是:
|
||||
|
||||
A. 仅保护对 L1 的写入操作
|
||||
B. 保护从 DB 查询并回填整个缓存链路的完整过程
|
||||
C. 仅保护对 L2 Redis 的 SET 操作
|
||||
D. 保护所有客户端的 GET 请求
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
在 TTL 随机化防雪崩的代码中,`baseMinutes=10, jitterPercent=30` 时,实际 TTL 落在 ______ 分钟到 ______ 分钟之间均匀分布。
|
||||
|
||||
> **提示**: actualMinutes = baseMinutes - jitter + rand.Intn(2*jitter),其中 jitter = baseMinutes * jitterPercent / 100。
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
空值缓存用于防护缓存穿透:查询结果为空时也缓存一个特殊标记(如 nil),设极短 TTL,通常范围为 ______ 秒到 2 分钟。
|
||||
|
||||
> **提示**: 回顾文档中的具体数值范围。
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
当多个应用实例同时修改同一个 key 导致缓存缺失时,会出现两个问题:重复重建和 ______ ,即 L1 缓存不知道 L2 已经被删除而继续提供旧数据。
|
||||
|
||||
> **提示**: 这是缓存一致性问题的另一个典型表现。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
一个新闻聚合 App 有以下特点:
|
||||
- 热门文章更新频率极低(小时级),但单次访问量大(万级 QPS)
|
||||
- 文章发布后需要尽快让所有用户看到最新版本
|
||||
- App 有多个服务实例分布在不同的可用区
|
||||
|
||||
请设计一个多级缓存方案,回答以下问题:
|
||||
1. L1、L2、L3 分别使用什么技术?为什么?
|
||||
2. 如何保证文章更新后各实例的 L1 缓存能尽快刷新?
|
||||
3. 如何防止大量缓存同时过期导致的雪崩?
|
||||
|
||||
> **答题框架提示**:
|
||||
> 1. 各层的技术选型依据
|
||||
> 2. 失效传播的具体方案(MQ/binlog/短TTL组合)
|
||||
> 3. TTL 随机化的具体参数建议
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | B | L1 Caffeine 基于 JVM 堆内内存,多实例间数据无法同步,每个实例有自己的缓存视图。A 错——L1 微秒级远低于 L2 毫秒级;C 错——支持 LRU、LFU、Window LFU 等;D 错——运行期可动态调整。 |
|
||||
| Q2 | B | 先删缓存再写 DB 是最危险的:写操作完成后、DB 写入前的窗口期内,读请求会拿到陈旧缓存(或者查到 DB 旧数据重写回缓存)。推荐先更新 DB 再删缓存,极端竞态概率更低。 |
|
||||
| Q3 | B | singleflight 的核心机制:对于同一个 key 的并发查询,只有一个 goroutine 执行 fn() 函数去查 DB,其他 goroutine 等待该结果并直接返回。这避免了大量并发请求同时穿透到数据库。 |
|
||||
| Q4 | B | A 反了——MQ 广播实时性好但不是最适合高实时场景;短 TTL 更适合容忍短暂不一致的场景。C 错误——定时扫描延迟高且成本高不是最优方案。D 错误——三种方案性能和架构差异明显。 |
|
||||
| Q5 | C | 计算方法:10000 × (1 - 0.9) × (1 - 0.8) = 10000 × 0.1 × 0.2 = 200 QPS。L1 拦截了 9000 QPS,剩余 1000 进入 L2,L2 拦截 800 条,最终 200 QPS 到达 Redis 甚至下游。这是评估集群规模的核心指标。 |
|
||||
| Q6 | B | singleflight.Group.Do(key, fn) 的作用范围是从 DB 查询到将结果写回 L1 和 L2 的完整链路。它确保同一时刻对同一 key 只有一个 DB 查询在执行,其余请求共享该结果。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | 7 | 13 | jitter = 10 * 30 / 100 = 3;actualMinutes = 10 - 3 + rand.Intn(2*3) = 7 ~ 13,均匀分布。这样可以避免大量 key 在同一时刻过期。 |
|
||||
| F2 | 30 | 空值缓存的 TTL 一般为 30s~2min。时间太短会让穿透攻击持续有效,太长则浪费存储和增加清理负担。适用于不存在的数据比例较低的场景。 |
|
||||
| F3 | 脏数据残留 | L1 缓存不知道 L2 已经被删除而继续提供旧数据,这就是"脏数据残留"问题。解决策略包括短 TTL 兜底、MQ 广播或 Canal binlog 监听等方式。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. L1 用 Caffeine(JVM 堆内内存,微秒级延迟),适合存最热点的几篇热门文章;L2 用 Redis(集中式共享缓存,毫秒级),存全量活跃文章;L3 用 MySQL(最终数据来源)。理由:读取频率极高但更新低频,L1 拦截 90%+ 流量。
|
||||
2. 失效传播采用 Canal + binlog 监听方案:文章发布后解析 MySQL binlog,自动推送 invalidate 事件给各实例清除对应文章的 L1 缓存。配合短 TTL(如 1 小时)加后台预刷作为兜底。
|
||||
3. TTL 随机化:在基础 TTL(如 10 分钟)上加减 30% 的随机偏移,使过期时间均匀分布在 7~13 分钟,避免集中过期。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖三层选型、失效传播方案和雪崩防护三个维度。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/core/Redis 五大核心数据结构]]
|
||||
- [[03.Redis/strategies/缓存穿透击穿雪崩解决方案]]
|
||||
- [[03.Redis/strategies/旁路缓存与读写策略]]
|
||||
- [[03.Redis/strategies/HeavyKeeper 热点探测算法]]
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
tags: [test/review, redis, cache-strategy, cache-aside, read-through, write-through]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# 旁路缓存与读写策略_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖四种经典的缓存读写策略:Cache Aside、Read Through、Write Through 和 Write Behind。共 10 道题:6 道选择题、3 道填空题、1 道简答题,考察对各自一致性模型、延迟特征和适用场景的理解。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
在 Cache Aside(旁路缓存)模式中,读操作的默认处理流程是:
|
||||
|
||||
A. 应用先写 DB,再从 DB 读取返回
|
||||
B. 先查缓存,命中直接返回;未命中查 DB 并将结果写入缓存
|
||||
C. 先查缓存,未命中直接查 DB 不写缓存
|
||||
D. 直接从 DB 读取并同时通知缓存层刷新
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
Cache Aside 模式在写操作时的标准动作是什么?
|
||||
|
||||
A. 先更新 DB,再删除缓存
|
||||
B. 先删除缓存,再更新 DB
|
||||
C. 先更新 DB,再更新缓存为新值
|
||||
D. 只更新 DB,不操作缓存
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
关于 Write Behind(异步回写)策略,以下哪项描述是正确的?
|
||||
|
||||
A. 写操作同时等待缓存和 DB 确认后才返回
|
||||
B. 写操作只写缓存,由后台线程异步批量刷盘到 DB
|
||||
C. 读操作始终直连 DB,不经过缓存
|
||||
D. 缓存和 DB 之间通过分布式锁保证强一致
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
下列哪种缓存策略在一致性保证上最强但在写延迟上最高?
|
||||
|
||||
A. Cache Aside —— 最终一致,中等延迟
|
||||
B. Read Through —— 强一致,miss 时有 DB RTT
|
||||
C. Write Through —— 强一致,写必须同步等 DB 确认
|
||||
D. Write Behind —— 弱一致,写延迟极低
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
某会话管理系统需要将用户的 Session 信息存储在缓存中作为主存储(非持久化存储)。以下哪种策略最合适?
|
||||
|
||||
A. Cache Aside,因为简单解耦
|
||||
B. Write Behind,追求极致写入速度
|
||||
C. Read Through + Write Through,因为缓存本身就是主存储
|
||||
D. 直接不用缓存,全部存数据库
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
在 Go 实现的 Cache Aside 中,为了缓解"写完后 DEL 之前并发读可能把旧值回填缓存"的竞态窗口,可以采用的变体策略是什么?
|
||||
|
||||
A. Cache Aside with Delayed Delete:写操作后延时几百毫秒再删缓存
|
||||
B. 在 DEL 之前加一个 sleep 固定等待 1 秒
|
||||
C. 改用 Mutex 锁保护整个写操作流程
|
||||
D. 删除缓存后再手动验证缓存是否真的被删除
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
在 Write Through 模式下,应用发送写请求后,只有收到 ______ 层的确认才算写入成功,由该层负责同步写入 DB。
|
||||
|
||||
> **提示**: 应用的写操作只和一层交互。
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
大多数通用场景的首选缓存策略是 ______ ,因为它简单、解耦、容错好,DB 是权威数据源。
|
||||
|
||||
> **提示**: 四个字母的缩写。
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
Write Behind 的风险在于:如果缓存层在尚未刷盘前宕机,该批次数据会 ______ 。
|
||||
|
||||
> **提示**: 这是对业务数据安全性的最大威胁。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
一个日志采集系统有以下需求:
|
||||
- 每秒接收百万级日志事件,每条都需记录
|
||||
- 日志数据不需要强一致性,延迟几分钟也可以接受
|
||||
- 系统必须保证极低的写入延迟以保证高吞吐
|
||||
- 偶尔丢失少量日志完全可接受
|
||||
|
||||
请按决策树思路分析应选择哪种缓存策略,并详细说明你的设计方案(包括如何处理缓存层故障导致的数据丢失风险)。
|
||||
|
||||
> **答题框架提示**:
|
||||
> 1. 根据需求逐层对照决策树
|
||||
> 2. 对比各策略的延迟和一致性特征
|
||||
> 3. 针对所选方案的缺陷提出补偿措施
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | B | Cache Aside 读流程:先看缓存,命中返回;未命中查 DB,并将结果 SET 回缓存(带 TTL)。这是最常见的缓存读写模式。 |
|
||||
| Q2 | A | Cache Aside 写的标准动作是先更新 DB 再删除缓存。注意不是更新缓存而是删除——下次读取时会从 DB 重建最新值。先删再写的方式存在竞态风险。 |
|
||||
| Q3 | B | Write Behind 只写缓存,后台线程异步批量刷盘到 DB。特点是极致性能但崩溃丢数据。A 描述的是 Write Through;C 和 D 都与 Write Behind 的设计相反。 |
|
||||
| Q4 | C | Write Through 写操作必须同步等待 DB 确认后返回,延迟最高但一致性最强——缓存和 DB 之间天然一致。Write Behind 虽然快但一致性弱(异步延迟);Cache Aside 最终一致。 |
|
||||
| Q5 | C | Session 本身就是主存储(非持久化),缓存层挂掉需要降级方案。这种情况下应考虑 Read Through + Write Through,因为缓存即主要数据源,Session 的生命周期由缓存管理。 |
|
||||
| Q6 | A | "Cache Aside with Delayed Delete"是一种变体:写操作后延时几百毫秒再删缓存,可以缓解部分竞态问题。但不适合高一致性要求场景,因为这段时间内缓存仍然可能是旧的。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | 缓存(CacheLayer) | Write Through 模式下应用只和缓存层交互,不直接接触 DB。缓存层接收到写请求后立即返回 OK(缓存写入成功),然后在内部异步同步到 DB。 |
|
||||
| F2 | Cache Aside | 面试标准答案——绝大多数场景首选 Cache Aside。它简单、解耦、容错好。只有当架构本身就是"缓存为主存储"时才考虑 Read Through + Write Through。 |
|
||||
| F3 | 丢失 | Write Behind 的崩溃丢数据风险是最大短板:缓存未刷盘前宕机,该批次数据永远丢失。需要复杂的故障恢复机制,因此只用于能接受短暂数据丢失的非关键场景。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. 选择 Write Behind 策略。决策思路:日志数据不需要强一致性(否)→ 能否接受写延迟(要极致速度)→ Write Behind。
|
||||
2. Write Behind 的优势:写操作只写缓存,后台线程异步批量刷盘到 ClickHouse/TimescaleDB,几乎无额外开销。
|
||||
3. 补偿措施:由于偶尔丢失少量日志可接受,所以 Write Behind 的风险可控。但可以增加 WAL(Write Ahead Log)机制,先将日志写入本地磁盘文件再异步刷盘,即使 crash 也能从本地 WAL 恢复未提交的数据。
|
||||
4. 批处理优化:后台线程每隔 N 毫秒或积攒 M 条消息后批量刷盘,减少 DB IO 次数。配合监控 unacked 数量和 Queue depth 发现趋势性增长立即扩容。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖策略选择、优势分析、补偿措施三个维度。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/strategies/多级缓存架构设计]]
|
||||
- [[03.Redis/strategies/缓存穿透击穿雪崩解决方案]]
|
||||
- [[03.Redis/strategies/HeavyKeeper 热点探测算法]]
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
tags: [test/review, redis, cache-penetration, cache-breakdown, cache-avalanche, bloom-filter]
|
||||
create time: 2026-08-09 12:00
|
||||
---
|
||||
|
||||
# 缓存穿透击穿雪崩解决方案_测试题
|
||||
|
||||
## 概述
|
||||
本测试覆盖 Redis 缓存三大经典问题——穿透(Penetration)、击穿(Breakdown)和雪崩(Avalanche)。共 10 道题:6 道选择题、3 道填空题、1 道简答题,考察对各自成因的区分理解和对应方案的设计能力。
|
||||
|
||||
---
|
||||
|
||||
## 一、选择题(6道,由浅入深)
|
||||
|
||||
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
||||
|
||||
### Q1(基础)— 考察定义层面
|
||||
|
||||
以下哪种现象描述的是"缓存穿透"?
|
||||
|
||||
A. 热点 key 过期时大量并发请求同时查到缓存为空,纷纷去查 DB
|
||||
B. 大量 key 在同一时刻过期导致请求洪水面涌向下流
|
||||
C. 恶意用户反复查询数据库中不存在的 key,每次缓存都 miss 直接打到 DB
|
||||
D. 单个 key 的访问频率远高于平均水平,超过 Redis 实例处理能力
|
||||
|
||||
### Q2(基础)— 考察行为判断
|
||||
|
||||
布隆过滤器(Bloom Filter)在查询某个 key 时返回"可能存在",这表示:
|
||||
|
||||
A. 该 key 一定存在于数据集中
|
||||
B. 该 key 一定不存在于数据集中
|
||||
C. 该 key 可能存在也可能不存在(允许 False Positive)
|
||||
D. 布隆过滤器出错了
|
||||
|
||||
### Q3(进阶)— 考察核心原理
|
||||
|
||||
关于布隆过滤器的误判率公式 `p ≈ (1 - e^(-kn/m))^k`,其中 m 是位数组大小,n 是元素数量,k 是哈希函数个数。当 m/n = 10、k = 7 时,误判率约为:
|
||||
|
||||
A. 50%
|
||||
B. 10%
|
||||
C. 0.8%
|
||||
D. 0.001%
|
||||
|
||||
### Q4(进阶)— 考察对比辨析
|
||||
|
||||
互斥锁方案和逻辑 TTL 方案用于防止缓存击穿,以下哪项对比是正确的?
|
||||
|
||||
A. 互斥锁方案一致性弱但性能开销大
|
||||
B. 逻辑 TTL 方案强一致且无锁开销
|
||||
C. 互斥锁方案强一致但有死锁风险需要超时机制
|
||||
D. 两者都能完全避免 DB 压力增加
|
||||
|
||||
### Q5(深入)— 考察场景推理
|
||||
|
||||
某社交媒体平台的点赞数存储在 Redis 中,特点是不存在无效 key(key 都是有效的),但热点帖子点赞数的读取量极高(QPS 达数万)。对于这种场景,最有效的防击穿策略是:
|
||||
|
||||
A. 布隆过滤器预加载所有有效 user_id
|
||||
B. 永不过期加后台异步刷新的逻辑 TTL 方案
|
||||
C. 多层队列接力 DLX 重试模式
|
||||
D. 将点赞数存入 MySQL 而不使用缓存
|
||||
|
||||
### Q6(深入)— 考察源码级别细节
|
||||
|
||||
在完整的防御型缓存获取方法中,三道防线分别是什么顺序?
|
||||
|
||||
A. 读缓存 → 布隆过滤器 → 分布式锁
|
||||
B. 分布式锁 → 读缓存 → 布隆过滤器
|
||||
C. 布隆过滤器 → 读缓存 → 分布式锁
|
||||
D. 读缓存 → 分布式锁 → 布隆过滤器
|
||||
|
||||
---
|
||||
|
||||
## 二、填空题(3道)
|
||||
|
||||
### F1 — 填空
|
||||
|
||||
根据布隆过滤器最优参数公式,当已知位数组大小 m 和元素数量 n 时,最优哈希函数个数 k = ______ × ln(2)。
|
||||
|
||||
> **提示**: 回顾公式中的关键变量关系。
|
||||
|
||||
### F2 — 填空
|
||||
|
||||
雪崩的核心成因是大量 key 设置相同的过期时间,到期时 ______ ,请求洪水般涌向下游数据库。
|
||||
|
||||
> **提示**: 用一个两字词语描述这个状态变化。
|
||||
|
||||
### F3 — 填空
|
||||
|
||||
互斥锁方案防止击穿的代码模式中,必须先做双重检查(Double Check):先尝试获取锁,成功后再从缓存读一次值,如果仍为 nil 才真正查 DB。这是因为其他线程可能已经在锁等待期间完成了重建。如果不做双重检查,会导致 ______ 。
|
||||
|
||||
> **提示**: 思考如果没有 double check 会重复执行什么操作。
|
||||
|
||||
---
|
||||
|
||||
## 三、简答题(1道)
|
||||
|
||||
### S1
|
||||
|
||||
一个电商平台的商品 SKU 查询系统有以下特征:
|
||||
- SKU ID 的范围已知且相对稳定(约 100 万个有效 ID)
|
||||
- 查询流量不稳定,有时会出现恶意扫描
|
||||
- 爆款商品单秒 QPS 可达 5000+
|
||||
- 运营人员频繁更新商品价格信息
|
||||
|
||||
请设计一套三层防御方案来保护后端数据库,回答以下问题:
|
||||
1. 每层使用什么技术?防护什么问题?
|
||||
2. 第一层布隆过滤器的参数如何设置(m、n、k)?
|
||||
3. TTL 随机化的具体参数建议是什么?
|
||||
|
||||
> **答题框架提示**:
|
||||
> 1. 穿透/击穿/雪崩三层对应的技术方案
|
||||
2. 布隆过滤器的参数计算
|
||||
3. TTL 参数选择和理由
|
||||
|
||||
---
|
||||
|
||||
## 参考答案与解析
|
||||
|
||||
### 选择题答案
|
||||
|
||||
| 题号 | 正确答案 | 解析 |
|
||||
|------|---------|------|
|
||||
| Q1 | C | 缓存穿透的定义:恶意或异常查询不断访问数据库中不存在的 key,每次缓存 miss 导致请求直达 DB。A 是击穿;B 是雪崩;D 是热点 Key 问题。 |
|
||||
| Q2 | C | 布隆过滤器的特性:返回"一定不在"表示必定不存在(No False Negative),返回"可能存在"表示可能在(可能有 False Positive)。这是空间效率的代价。 |
|
||||
| Q3 | C | 当 m/n = 10、k = 7 时,误判率约 0.8%。这是面试常考的经典参数组合。增大 m/n 可进一步降低误判率(如 m/n = 20 时降至 0.02%)。 |
|
||||
| Q4 | C | 互斥锁方案保证强一致(新值写入后才可读到),但存在死锁风险——如果重建过程抛出异常没有释放锁,其他线程永远阻塞。必须用 defer unlock 或超时机制。B 错误——逻辑 TTL 是最终一致而非强一致。 |
|
||||
| Q5 | B | 点赞数几乎不可能穿透(key 都是有效的),重点防击穿。永不过期加后台异步刷新(逻辑 TTL)方案最合适——物理上不设过期时间,内存维护逻辑过期时间,发现过期后异步触发重建,读取到的仍是旧值(无脑 hit),直到新缓存写入成功。 |
|
||||
| Q6 | C | 正确的三层防线顺序:第1道——布隆过滤器拦截不存在的 key;第2道——读缓存命中直接返回;第3道——分布式锁防止击穿,只有一个线程查 DB 回填。这个顺序确保最轻量级的检查在最前面。 |
|
||||
|
||||
### 填空题答案
|
||||
|
||||
| 题号 | 答案 | 解析 |
|
||||
|------|------|------|
|
||||
| F1 | m/n | 最优哈希函数数 k = (m/n) * ln(2),此时误判率最低。ln(2) ≈ 0.693,所以 k 约等于 0.693 * m/n。 |
|
||||
| F2 | 集中失效 | 大量 key 同时过期意味着原本被缓存拦截的请求突然全部穿透到数据库,形成集中的查询洪峰,超出系统的承载能力。通过在 TTL 上加随机偏移可以分散过期时间点。 |
|
||||
| F3 | 重复查 DB / 重复重建 | 双重检查确保:即使成功获得锁,也要再读一次缓存——因为在这之前另一个已经持有锁的线程可能已完成重建并写回缓存。不做双重检查会浪费一次 DB 查询。 |
|
||||
|
||||
### 简答题参考答案
|
||||
|
||||
S1:**参考答案要点**:
|
||||
1. 第一层——布隆过滤器:预加载所有 100 万有效 SKU ID,拦截不存在的 key 查询,防止穿透。第二层——互斥锁:对热点商品 sku 加分布式锁,防止击穿。第三层——TTL 随机化:设置基础 TTL 加减随机波动,防止雪崩。
|
||||
2. 布隆过滤器参数:n = 100万,取 m/n = 10 则 m = 1000万位(约 1.2MB),k = 7。误判率约 0.8%,对业务可接受。也可取 m/n = 20 进一步降低到 0.02%。
|
||||
3. TTL 随机化:商品价格在运营更新后立即写入 DB 并 DEL 缓存,下次读取时 SET 一个新的带随机抖动的 TTL。建议 baseMinutes=10, jitterPercent=30,使实际 TTL 落在 [7, 13] 分钟均匀分布。
|
||||
|
||||
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖三层防御方案、布隆参数计算和 TTL 策略三个维度。
|
||||
|
||||
## 关联笔记
|
||||
- [[03.Redis/strategies/多级缓存架构设计]]
|
||||
- [[03.Redis/strategies/旁路缓存与读写策略]]
|
||||
- [[03.Redis/strategies/HeavyKeeper 热点探测算法]]
|
||||
Reference in New Issue
Block a user