Files
examination/topics/thumbup/two-level-cache/short_answer.json
T

78 lines
5.8 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"topic": "two-level-cache",
"type": "short_answer",
"schema_version": "1.0.0",
"generated": "2026-09-09T21:42:00+08:00",
"questions": [
{
"id": "sa-001",
"type": "short_answer",
"difficulty": 3,
"tags": [
"two-level-cache",
"CacheManager",
"get-flow"
],
"question": "请完整描述 CacheManager.get() 的查询流程,说明每一步的目的。",
"answer": "CacheManager.get() 的完整流程如下:\n1. 查询 L1(Caffeine 本地缓存):命中则直接返回,这是最快的路径,零网络开销。\n2. 查询空值缓存(null cache):如果 Key 被标记为空值缓存,说明之前确认 Redis 中不存在该 Key,直接返回 null,防止缓存穿透。\n3. 加锁并 double-check:获取 Key 级别的锁后,重新检查 L1 和空值缓存,防止并发请求重复穿透(防止惊群效应)。\n4. 查询 Redis(L2 缓存):从 Redis 获取数据。\n5. 写入空值缓存或 HeavyKeeper 记录:Redis 返回 null 则写入空值缓存防止穿透;Redis 有数据则通过 HeavyKeeper.add() 记录访问频次。\n6. 热 Key 提升 L1:如果 HeavyKeeper.isHotKey() 返回 true,将数据写入 Caffeine,后续相同 Key 的请求可在 L1 直接命中。",
"explanation": "",
"keywords": [
"L1",
"Caffeine",
"空值缓存",
"double-check",
"Redis",
"HeavyKeeper",
"热Key提升"
],
"scoring_rubric": "满分10分。描述完整6步流程得8分,每少一步扣1.5分。每步有正确的目的说明额外加0.5分,最多加2分。出现概念性错误(如将空值缓存描述为缓存 Redis 空值对象)扣2分。"
},
{
"id": "sa-002",
"type": "short_answer",
"difficulty": 4,
"tags": [
"HeavyKeeper",
"fading",
"decay",
"parameters"
],
"question": "请解释 HeavyKeeper 的双重衰减机制(add() 中的冲突衰减和 fading() 的周期衰减)是如何协同工作的,以及为什么要同时使用两种衰减。",
"answer": "HeavyKeeper 的双重衰减机制包括:\n\n1. add() 中的冲突衰减(per-operation decay):当新 Key 的 fingerprint 与桶中已有 Key 冲突时,以概率 decay(0.92)对已有 count 执行 -1 操作。这是一种细粒度的、事件驱动的衰减,每次冲突都可能淘汰冷 Key。\n\n2. fading() 的周期衰减(periodic decay):每 20 秒对所有桶的 count 执行右移一位(除以 2)。这是一种粗粒度的、时间驱动的衰减,确保热度统计反映的是近期行为而非历史累积。\n\n协同工作原理:\n- 如果只有冲突衰减:冷 Key 不会被冲突到的桶永远不会衰减,导致历史热 Key 永远占据桶位。\n- 如果只有周期衰减:两个冷 Key 之间不会互相淘汰,桶位被低频 Key 长期占用。\n- 两者结合:冲突衰减在局部竞争中快速淘汰冷 Key,周期衰减在全局范围内将所有 Key 的热度重置为半衰期模型。冲突衰减处理 Key 之间的横向竞争,fading() 处理时间维度的纵向衰减。这种设计让 HeavyKeeper 既能快速感知热度变化,又能防止历史数据干扰。",
"explanation": "",
"keywords": [
"冲突衰减",
"周期衰减",
"decay",
"fading",
"right-shift",
"半衰期"
],
"scoring_rubric": "满分10分。正确描述两种衰减各2分(共4分)。正确分析只有一种衰减时的缺陷各1.5分(共3分)。正确说明协同工作原理2分。表述清晰、逻辑连贯1分。出现概念性错误(如将右移描述为除以4)扣2分。"
},
{
"id": "sa-003",
"type": "short_answer",
"difficulty": 5,
"tags": [
"two-level-cache",
"inconsistency",
"L1-L2",
"write-consistency"
],
"question": "在分布式环境下,Caffeine + Redis 二级缓存面临哪些数据不一致风险?请分别从「写后读」和「并发写」两个场景分析,并给出至少两种缓解方案。",
"answer": "一、写后读不一致风险:\n当一个实例更新/删除 Redis 后,需要通知其他实例失效本地 Caffeine。在通知发出到其他实例收到并执行失效之间,存在时间窗口。在这个窗口内,其他实例的 Caffeine 仍持有旧数据,读请求会返回过期值。\n\n二、并发写不一致风险:\n多个实例同时更新同一 Key 时,由于操作顺序不可控(实例 A 先删 L1 后写 Redis,实例 B 可能在 A 写 Redis 之前读到旧 L1 并缓存),可能导致最终 L1 和 L2 数据不一致。如果使用 putIfPresent 还可能导致合法更新被拒绝。\n\n缓解方案:\n1. 延迟双删:更新 Redis 后先失效 L1,sleep 一段时间后再次失效 L1,确保窗口期的脏数据被清理。\n2. 消息队列通知:使用可靠的 MQ(如 RocketMQ)替代 Redis Pub/Sub 发送失效通知,保证通知不丢失。\n3. 版本号/CAS:在 Redis 中为每个 Key 维护版本号,读取时校验版本号,版本不一致则重新加载。\n4. 短 TTL 兜底:L1 的 expireAfterWrite 设置较短(如几秒),即使通知丢失,脏数据也会很快自然过期。\n5. 订阅 Redis 消费 binlog:使用 Canal 等工具监听 Redis 的数据变更事件,实现最终一致。",
"explanation": "",
"keywords": [
"写后读",
"并发写",
"延迟双删",
"消息队列",
"版本号",
"TTL",
"Canal"
],
"scoring_rubric": "满分10分。正确描述写后读不一致场景2分。正确描述并发写不一致场景2分。给出至少两种有效缓解方案,每种1.5分(最多6分)。方案描述含糊或有概念错误扣1分。超过5种方案不再加分。"
}
]
}