216 lines
9.1 KiB
JSON
216 lines
9.1 KiB
JSON
{
|
|
"topic": "cache-read-write-strategy",
|
|
"type": "single_choice",
|
|
"schema_version": "1.0.0",
|
|
"generated": "09-2026-09-05T00:00:00+08:00",
|
|
"questions": [
|
|
{
|
|
"id": "sc-001",
|
|
"type": "single_choice",
|
|
"difficulty": 1,
|
|
"tags": [
|
|
"Cache-Aside",
|
|
"写策略"
|
|
],
|
|
"question": "Cache-Aside(旁路缓存)策略中,写操作的标准流程是?",
|
|
"options": {
|
|
"A": "先删除缓存,再写数据库",
|
|
"B": "先写数据库,再失效(删除)缓存",
|
|
"C": "只写缓存,不同步数据库",
|
|
"D": "先写缓存,再同步写数据库"
|
|
},
|
|
"answer": "B",
|
|
"explanation": "Cache-Aside 的主流写法是「先写 DB(真相源),再删除缓存」,下次读请求 miss 后重新查库回填。选项 A 是容易引入不一致的反模式,C 会丢失数据,D 是 Write-Through 的做法。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-002",
|
|
"type": "single_choice",
|
|
"difficulty": 1,
|
|
"tags": [
|
|
"Cache-Aside",
|
|
"CacheLoader",
|
|
"Read-Through"
|
|
],
|
|
"question": "Cache-Aside 策略中,读请求发生缓存 miss 时的正确处理是?",
|
|
"options": {
|
|
"A": "直接返回空,不访问数据库",
|
|
"B": "由应用自行查询数据库,并把结果回填(写入)缓存",
|
|
"C": "只把数据返回给调用方,不回填缓存",
|
|
"D": "删除该 key 对应的缓存后再返回"
|
|
},
|
|
"answer": "B",
|
|
"explanation": "Cache-Aside 的读路径:先查缓存,miss 后由应用查数据库,拿到结果后回填缓存并返回。这样后续相同 key 的读请求都能命中缓存,避免每次打库。C 不回填会导致缓存失去意义。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-003",
|
|
"type": "single_choice",
|
|
"difficulty": 3,
|
|
"tags": [
|
|
"Write-Through",
|
|
"Write-Behind",
|
|
"一致性"
|
|
],
|
|
"question": "关于 Write-Through(写穿透)与 Write-Behind(异步回写)的区别,以下说法正确的是?",
|
|
"options": {
|
|
"A": "Write-Through 先写缓存后异步批量刷库,吞吐高但可能丢数据",
|
|
"B": "Write-Behind 写缓存即同步写数据库,强一致但慢",
|
|
"C": "Write-Through 是「先写缓存再同步写数据库」,一致性较强但写性能较低",
|
|
"D": "两种策略在写路径上完全等价"
|
|
},
|
|
"answer": "C",
|
|
"explanation": "Write-Through 写缓存即同步写 DB,一致性较强但慢;Write-Behind 写进缓存就先返回,DB 由后台异步批量刷写,吞吐高但有丢失/乱序风险。A、B 把两者对调混淆了,D 错误。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-004",
|
|
"type": "single_choice",
|
|
"difficulty": 3,
|
|
"tags": [
|
|
"Write-Behind",
|
|
"Write-Back",
|
|
"数据安全"
|
|
],
|
|
"question": "对于交易、余额、库存这类强一致+不可丢失的数据,下列哪种缓存策略是绝不可取的?",
|
|
"options": {
|
|
"A": "Cache-Aside(先写库再删缓存)",
|
|
"B": "Write-Through(写库即同步写库)",
|
|
"C": "Write-Behind(异步批量落库)",
|
|
"D": "Read-Through(读缓存 miss 自动查库回填)"
|
|
},
|
|
"answer": "C",
|
|
"explanation": "Write-Behind/Write-Back 采用异步批量刷库,存在缓存宕机导致写入丢失、乱序、外部读到旧值等风险,只适合最终一致+可容忍丢失的数据(计数、会话、中间态),绝不可用于交易/余额/库存这类数据。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-005",
|
|
"type": "single_choice",
|
|
"difficulty": 3,
|
|
"tags": [
|
|
"多级缓存",
|
|
"L1",
|
|
"L2"
|
|
],
|
|
"question": "经典的多级缓存结构中,本地进程内缓存(如 Caffeine)相比共享 Redis 缓存的优势是?",
|
|
"options": {
|
|
"A": "可以跨节点共享数据,重启后不丢失",
|
|
"B": "零网络开销,读取延迟最低,能扛住超高 QPS",
|
|
"C": "支持持久化和主从集群",
|
|
"D": "数据全自动与数据库保持一致"
|
|
},
|
|
"answer": "B",
|
|
"explanation": "L1 本地进程内缓存(Caffeine)读不走网络,延迟最低,适合热点 key 的超高 QPS;代价是单机副本、重启即失、跨节点不一致。A、C 是 Redis(L2 共享)的优势,D 在所有缓存策略下都不成立。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-006",
|
|
"type": "single_choice",
|
|
"difficulty": 4,
|
|
"tags": [
|
|
"多级缓存",
|
|
"一致性"
|
|
],
|
|
"question": "多级缓存架构中最需要付出代价、也是最难解决的是?",
|
|
"options": {
|
|
"A": "缓存容量不足",
|
|
"B": "各级缓存的命中率",
|
|
"C": "同一数据在多个缓存层/多个节点间的一致性",
|
|
"D": "本地缓存占用内存过大"
|
|
},
|
|
"answer": "C",
|
|
"explanation": "多级缓存里同一数据会同时存在于 L1 本地、L2 Redis、多个节点上,更新时任一副本残留旧值都会造成不一致。所以一致性是多级缓存最大的代价,需要通过广播失效、短 TTL、回调等机制缓解。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-007",
|
|
"type": "single_choice",
|
|
"difficulty": 4,
|
|
"tags": [
|
|
"Caffeine",
|
|
"TinyLFU",
|
|
"淘汰算法"
|
|
],
|
|
"question": "Caffeine 本地缓存的默认淘汰/近似最合适算法是 TinyLFU,它与 LRU 相比的主要改进是?",
|
|
"options": {
|
|
"A": "淘汰顺序完全随机,降低计算开销",
|
|
"B": "只按访问时间决定淘汰,实现更简单",
|
|
"C": "基于访问频率(并辅以最近访问时间/衰减),能保留短期突发但长期高频的 key,避免 LRU 被顺序扫描冲垮",
|
|
"D": "淘汰后立即删除数据库副本,保证一致性"
|
|
},
|
|
"answer": "C",
|
|
"explanation": "TinyLFU 通过维护计数器记录 key 的访问频率,并结合时间窗口衰减与最近访问,避免 LRU 被「批量顺序扫描」一次性清空高频 key,淘汰决策更聪明。A、B 描述错误,D 属其他机制。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-008",
|
|
"type": "single_choice",
|
|
"difficulty": 4,
|
|
"tags": [
|
|
"版本号回填",
|
|
"竞态"
|
|
],
|
|
"question": "Cache-Aside「先写库再删缓存」的竞态窗口,最恶劣的后果是?",
|
|
"options": {
|
|
"A": "某个读请求多一次数据库查询",
|
|
"B": "删缓存操作偶尔失败",
|
|
"C": "读线程在删缓存之后把库里的旧值回填进缓存,导致旧值长期残留缓存",
|
|
"D": "写请求直接丢失数据"
|
|
},
|
|
"answer": "C",
|
|
"explanation": "最严重的是「来源2」:读线程在写线程删缓存的时刻库仍是旧值,读操作回填,且可能发生在删除之后,于是旧值被写进缓存且无法用「删缓存」治愈。它会导致后续所有读请求都命中旧值,直到 TTL 到期或再次写。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-009",
|
|
"type": "single_choice",
|
|
"difficulty": 4,
|
|
"tags": [
|
|
"双删",
|
|
"版本号回填",
|
|
"竞态"
|
|
],
|
|
"question": "下列哪种方案可以从「在缓存中残留旧值」这一类竞态问题,适合强一致且不易辩的 key?",
|
|
"options": {
|
|
"A": "删除缓存前先 sleep 100ms",
|
|
"B": "写成功后连删两次缓存(双删)",
|
|
"C": "缓存中带版本号,回填前比较,旧版本直接丢弃",
|
|
"D": "写入缓存后就放弃数据库"
|
|
},
|
|
"answer": "C",
|
|
"explanation": "版本号回填会在回填前比较缓存中的版本号与要写入的一致性,旧版本直接丢弃,因此从根源上避免「旧值被回填」——这也是“版本号是最彻底的方案”的原因。双删只能提高第二次删除覆盖旧可能,不能保证绝对干净。",
|
|
"source": null,
|
|
"related": []
|
|
},
|
|
{
|
|
"id": "sc-010",
|
|
"type": "single_choice",
|
|
"difficulty": 4,
|
|
"tags": [
|
|
"Write-Behind",
|
|
"乱序",
|
|
"丢失"
|
|
],
|
|
"question": "Write-Behind(异步回写)存在多种风险,下列哪一项是 Write-Behind 独有、Cache-Aside 也会出现但策略不同?",
|
|
"options": {
|
|
"A": "缓存 miss 后需要回填",
|
|
"B": "异步落库可能存在「缓存宕机且落库前数据丢失」",
|
|
"C": "删缓存后读线程仍读库",
|
|
"D": "本地缓存无法节点间共享"
|
|
},
|
|
"answer": "B",
|
|
"explanation": "「先写缓存即返回成功、DB 后台异步刷」是 Write-Behind 的核心,因此若缓存进程在落库前宕机,这些更新就丢了。这是 Write-Behind 独有的风险,也再次印证它只适合可丢失的最终一致数据。",
|
|
"source": null,
|
|
"related": []
|
|
}
|
|
]
|
|
}
|