{ "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": [] } ] }