176 lines
8.3 KiB
JSON
176 lines
8.3 KiB
JSON
{
|
||
"topic": "two-level-cache",
|
||
"type": "single_choice",
|
||
"schema_version": "1.0.0",
|
||
"generated": "2026-09-09T21:42:00+08:00",
|
||
"questions": [
|
||
{
|
||
"id": "sc-001",
|
||
"type": "single_choice",
|
||
"difficulty": 2,
|
||
"tags": [
|
||
"two-level-cache",
|
||
"CacheManager",
|
||
"architecture"
|
||
],
|
||
"question": "在 Caffeine + Redis 二级缓存架构中,CacheManager.get() 的第一步查询目标是什么?",
|
||
"options": {
|
||
"A": "直接查询 Redis(L2 缓存)",
|
||
"B": "查询 Caffeine 本地缓存(L1 缓存)",
|
||
"C": "查询空值缓存(null cache)",
|
||
"D": "加锁后执行 double-check 查询 Redis"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "CacheManager.get() 的完整流程为:查 L1(Caffeine)→ 查空值缓存 → 加锁 double-check → 查 Redis → HeavyKeeper 记录 → 热 Key 提升 L1。第一步始终是查询本地 Caffeine 缓存,命中则直接返回,未命中才进入后续流程。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-002",
|
||
"type": "single_choice",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"two-level-cache",
|
||
"null-cache",
|
||
"lock"
|
||
],
|
||
"question": "CacheManager.get() 流程中,空值缓存(null cache)的作用是什么?",
|
||
"options": {
|
||
"A": "缓存 Redis 中存储的空值对象,防止穿透",
|
||
"B": "记录已查询过但 Redis 中不存在的 Key,避免重复穿透到 Redis",
|
||
"C": "作为 L1 和 L2 之间的一致性校验缓存",
|
||
"D": "存储 HeavyKeeper 探测到的热 Key 列表"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "空值缓存用于记录已确认在 Redis 中不存在的 Key,后续相同 Key 的查询命中空值缓存后直接返回,不再穿透到 Redis,有效防止缓存穿透。注意这里缓存的不是 Redis 中的空值对象,而是对 Key 不存在这一事实的标记。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-003",
|
||
"type": "single_choice",
|
||
"difficulty": 2,
|
||
"tags": [
|
||
"Caffeine",
|
||
"W-TinyLFU",
|
||
"eviction"
|
||
],
|
||
"question": "Caffeine 使用的 W-TinyLFU 淘汰策略中,「W」代表什么含义?",
|
||
"options": {
|
||
"A": "Write-through(写穿透)",
|
||
"B": "Window(窗口)",
|
||
"C": "Weighted(加权)",
|
||
"D": "Waiting(等待)"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "W-TinyLFU 中的 W 代表 Window,即窗口缓存。W-TinyLFU 将缓存分为 Window(窗口)和 Main(主)两个区域。新数据先进入 Window 区,通过准入策略(admission filter)决定是否提升到 Main 区。Main 区内部再分为 TinyLFU 的 probation 和 protected 两个子区。这种分层设计兼顾了频率统计精度和内存效率。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-004",
|
||
"type": "single_choice",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"HeavyKeeper",
|
||
"hot-key",
|
||
"detection"
|
||
],
|
||
"question": "HeavyKeeper 热 Key 探测算法的 add() 方法在遇到桶冲突时,执行的是什么操作?",
|
||
"options": {
|
||
"A": "直接覆盖桶中原有的 fingerprint 和 count",
|
||
"B": "累加已有 fingerprint 对应桶的 count",
|
||
"C": "以概率 decay 对已有 count 进行衰减,若衰减后 count < minCount 则替换为新 fingerprint,否则保留原 fingerprint",
|
||
"D": "将冲突 Key 放入溢出队列,异步处理"
|
||
},
|
||
"answer": "C",
|
||
"explanation": "HeavyKeeper 的 add() 有三个分支:1) 桶为空时直接写入 fingerprint 和 count=1;2) fingerprint 匹配时直接 count++;3) 桶冲突时以概率 decay 对已有 count 衰减,衰减后若 count < minCount(阈值为 10)则替换为当前 fingerprint 和 count=1,否则保留原 fingerprint。这一设计利用概率衰减自然淘汰冷 Key,保留热 Key。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-005",
|
||
"type": "single_choice",
|
||
"difficulty": 4,
|
||
"tags": [
|
||
"HeavyKeeper",
|
||
"fading",
|
||
"decay"
|
||
],
|
||
"question": "HeavyKeeper 的 fading() 方法每 20 秒执行一次,其核心操作是什么?",
|
||
"options": {
|
||
"A": "清空所有桶的 count,重新统计",
|
||
"B": "将所有桶的 count 右移一位(等效于除以 2,减半衰减)",
|
||
"C": "只对 count > minCount 的桶执行衰减",
|
||
"D": "删除所有 fingerprint 为空的桶"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "fading() 每 20 秒触发一次,对 HeavyKeeper 的所有 bucket 的 count 执行右移一位操作,即 count >>= 1,等效于将所有计数值减半。这实现了时间维度的指数衰减,使 HeavyKeeper 能够反映 Key 的近期热度而非累积热度。配合 add() 中的冲突衰减(decay=0.92),形成双重衰减机制。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-006",
|
||
"type": "single_choice",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"two-level-cache",
|
||
"write-consistency",
|
||
"putIfPresent"
|
||
],
|
||
"question": "在二级缓存架构中,更新数据时使用 putIfPresent 写入 Redis 的主要目的是什么?",
|
||
"options": {
|
||
"A": "提高写入性能,减少 Redis 操作次数",
|
||
"B": "保证只有在 Key 已存在时才更新,防止误覆盖其他线程新写入的数据",
|
||
"C": "确保 Redis 和 Caffeine 的写入顺序一致",
|
||
"D": "自动触发 L1 缓存的失效通知"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "putIfPresent 是 Redis 的条件写入命令,只有当 Key 已存在时才会执行更新操作并返回 OK,否则返回 nil。在二级缓存更新场景中,这可以防止并发更新时的误覆盖问题——如果另一个线程已经删除并重建了该 Key,当前线程的写入不会覆盖新数据。这是一种乐观锁思路的写一致性保障。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-007",
|
||
"type": "single_choice",
|
||
"difficulty": 4,
|
||
"tags": [
|
||
"two-level-cache",
|
||
"inconsistency",
|
||
"L1-L2"
|
||
],
|
||
"question": "在 Caffeine + Redis 二级缓存中,以下哪种场景最容易导致 L1 和 L2 之间的数据不一致?",
|
||
"options": {
|
||
"A": "L1 缓存容量设置过小导致频繁淘汰",
|
||
"B": "多个应用实例同时更新 Redis 中的同一 Key,但各实例的 Caffeine 失效通知未及时送达",
|
||
"C": "Caffeine 的 expireAfterWrite 设置过长",
|
||
"D": "Redis 采用主从架构导致读写延迟"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "多实例场景下,当一个实例更新了 Redis 数据后,需要通过消息广播(如 Redis Pub/Sub、RocketMQ 等)通知其他实例失效本地 Caffeine 缓存。如果通知延迟或丢失,其他实例的 Caffeine 中仍持有旧数据,而 Redis 已经是新数据,造成 L1/L2 不一致。这是分布式二级缓存最经典的一致性问题。A 和 C 影响的是缓存命中率,D 影响的是主从一致性而非 L1/L2 一致性。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "sc-008",
|
||
"type": "single_choice",
|
||
"difficulty": 5,
|
||
"tags": [
|
||
"HeavyKeeper",
|
||
"parameters",
|
||
"configuration"
|
||
],
|
||
"question": "HeavyKeeper 当前配置为 k=100, width=100000, depth=5, decay=0.92, minCount=10。当一个新 Key 被 add() 时,该 Key 的 fingerprint 需要经过几个哈希函数映射到几个桶中进行检测?",
|
||
"options": {
|
||
"A": "1 个(随机选一个桶)",
|
||
"B": "5 个(depth 个桶)",
|
||
"C": "100 个(k 个桶)",
|
||
"D": "100000 个(width 个桶)"
|
||
},
|
||
"answer": "B",
|
||
"explanation": "HeavyKeeper 中,depth 决定了每个 Key 需要检测的桶数量。对于每个被 add 的 Key,先计算其 fingerprint,然后通过 depth(此处为 5)个不同的哈希函数映射到 width 个桶中的 5 个位置。在这 5 个桶中,按 add() 的三分支逻辑处理。depth 越大,对热 Key 的识别精度越高,但内存开销也相应增加。k 是返回的 Top-K 热 Key 数量,width 是桶数组大小,decay 和 minCount 控制衰减和淘汰。",
|
||
"source": null,
|
||
"related": []
|
||
}
|
||
]
|
||
} |