{ "topic": "cache-three-problems", "type": "short_answer", "schema_version": "1.0.0", "generated": "2026-09-05T00:00:00+08:00", "questions": [ { "id": "sa-001", "type": "short_answer", "difficulty": 2, "tags": [ "缓存穿透" ], "question": "什么是缓存穿透?它的根本危害是什么?", "answer": "缓存穿透指查询一个数据库中也【不存在】的数据。缓存查不到、DB 也查不到,无法写回缓存,导致每一次请求都直达 DB。其根本危害是:反复回源会对数据库造成持续高压,恶意攻击者用大量不存在的 ID 请求即可把 DB 打垮。", "keywords": [ "不存在", "每次请求", "直达", "数据库", "打垮DB", "穿透" ], "scoring_rubric": "正确给出定义(查询不存在的数据)得 40 分;说明缓存无法写回导致每次直达 DB 得 30 分;点出对 DB 持续高压的危害得 30 分。", "explanation": "穿透的本质是「没数据可缓存」,所以常规 miss→回源流程失效,必须前置拦截。", "source": null, "related": [] }, { "id": "sa-002", "type": "short_answer", "difficulty": 2, "tags": [ "缓存穿透", "参数校验", "布隆过滤器", "缓存空对象" ], "question": "缓存穿透的常见解决方式有哪些?请至少列出三种并简述原理。", "answer": "1) 参数校验:在入口拦截非法/明显不存在的参数;2) 缓存空对象:DB 查不到也缓存 null + 极短 TTL,防止同一 key 反复穿透;3) 布隆过滤器:请求先过布隆过滤器,判断不存在直接拦截不查 DB(无假阴性);4) 还可以在数据库层做兜底校验等。", "keywords": [ "参数校验", "缓存空对象", "布隆过滤器", "空值", "短TTL", "拦截" ], "scoring_rubric": "答出每种正确方案得 30 分(共可答多但至少三种得 90 分);能说明每种原理(为何能防穿透)再得 10 分。", "explanation": "穿透的关键是「根本不存在」,所以必须前置拦截(参数校验、布隆)或对空结果做缓存(空对象)避免重复回源。", "source": null, "related": [] }, { "id": "sa-003", "type": "short_answer", "difficulty": 1, "tags": [ "缓存击穿" ], "question": "简述缓存击穿的定义与它的典型场景特征。", "answer": "缓存击穿指一个特别热门的热点 key(如微博热搜)缓存刚好过期的瞬间,大量请求同时 miss 全部打到一个数据库。特征是【单 key、高并发、瞬时尖峰】——只有那一个 key 失效,却因并发极高而冲垮 DB。", "keywords": [ "单个热点key", "过期", "高并发", "瞬时尖峰", "打数据库", "单key" ], "scoring_rubric": "正确界定为单个热点 key 过期得 50 分;写出大量请求同时回源打 DB 得 30 分;总结单 key/高并发/瞬时尖峰特征得 20 分。", "explanation": "击穿针对的是「单 key」,这一点是与雪崩(多 key)最关键的区分。", "source": null, "related": [] }, { "id": "sa-004", "type": "short_answer", "difficulty": 2, "tags": [ "缓存击穿", "互斥锁" ], "question": "用互斥锁解决缓存击穿的思路是什么?请说明加锁与不加锁的区别。", "answer": "思路:当热点 key 过期且多个请求同时请求时,只有【一个请求拿到锁(如 SETNX)去数据库回源并回填缓存】,其他请求阻塞等待锁释放后命中缓存,或直接返回旧值/降级。区别:不加锁时 N 个请求会同时打 DB 造成数据库瞬时峰值;加锁后同一时刻只有一个请求回源,其余请求等待或命中缓存,从而把峰值压平,保护 DB。", "keywords": [ "互斥锁", "SETNX", "只有一个回源", "其余等待", "命中缓存", "保护数据库" ], "scoring_rubric": "正确说明只有一个请求回源并回填得 50 分;解释其余请求等待/命中缓存得 30 分;说明加锁与不加锁的区别得 20 分。", "explanation": "互斥锁的核心是把「N 个线程同时回源」收敛为「1 个线程回源 + 其余等待」,通过牺牲少量延迟换取数据库安全。", "source": null, "related": [] }, { "id": "sa-005", "type": "short_answer", "difficulty": 3, "tags": [ "缓存击穿", "逻辑过期" ], "question": "说明「逻辑过期」方案解决缓存击穿的原理,并指出它相比真删除的一个关键优势。", "answer": "逻辑过期不物理删除 key,而是在 value 中额外记录一个逻辑过期时间。请求发现逻辑过期后不直接删除,而是由后台异步线程去更新数据并重设缓存;期间返回的仍是旧值持续可用。关键优势:在缓存「看似已过期」到「后台刷新完成」的窗口内,请求依旧能命中缓存而无需全部穿透到 DB,从而挡住并发瞬时冲击。", "keywords": [ "逻辑过期时间", "后台异步", "异步线程", "旧值可用", "不删缓存", "挡并发" ], "scoring_rubric": "正确描述不删除 key 且记录逻辑过期时间得 40 分;说明由后台异步线程刷新得 30 分;写出返回旧值/持续可用、挡住并发 的认知得 30 分。", "explanation": "逻辑过期用「数据可用性优先」避免窗口期打 DB,代价是可能短暂读到旧数据,适合对实时性要求不极端的场景。", "source": null, "related": [] }, { "id": "sa-006", "type": "short_answer", "difficulty": 3, "tags": [ "缓存雪崩", "TTL加随机" ], "question": "缓存雪崩的成因有哪些?分别说明其对应的治理思路。", "answer": "成因一:一大批 key 同时设置了相同过期时间,到了同一时刻集体失效;对应思路——给 TTL 加随机偏差打散到期时间。成因二:缓存服务整机宕机导致所有请求穿透;对应思路——多级缓存兜底、加锁/限流/熔断降级保护 DB,以及 Redis 主从+哨兵+集群的高可用部署。", "keywords": [ "相同过期时间", "集体失效", "加随机", "宕机", "多级缓存", "限流降级", "高可用" ], "scoring_rubric": "正确识别集体失效并给出加随机策略得 40 分;正确识别宕机并给出兜底/限流/降级得 35 分;给出 Redis 高可用部署得 25 分。", "explanation": "雪崩是「批量」失效叠加「整机宕机」两大风险,治理必须组合 TTL 打散 + 多层兜底 + 限流降级 + 高可用。", "source": null, "related": [] }, { "id": "sa-007", "type": "short_answer", "difficulty": 1, "tags": [ "缓存穿透", "缓存击穿", "缓存雪崩" ], "question": "缓存穿透、击穿、雪崩三者的核心区别是什么?", "answer": "穿透——根因是查询【不存在】的数据,缓存永远 miss,DB 持续高压,解法是空值缓存+布隆过滤器+参数校验;击穿——单个热点 key 刚过期遭遇高并发,DB 瞬时尖峰,解法是互斥锁+逻辑过期;雪崩——大批 key 同时过期或缓存宕机,多 key 集体失效冲垮 DB,解法是 TTL 加随机+多级缓存+限流降级+Redis 高可用。", "keywords": [ "不存在", "单key", "多key", "持续高压", "瞬时尖峰", "集体失效" ], "scoring_rubric": "准确定义穿透并答对应对策 33 分;准确定义击穿并答对 33 分;准确定义雪崩并答对 34 分。", "explanation": "记忆口诀:穿透查「空」,击穿防「单点失效」,雪崩防「批量失效/宕机」。三者最终都要保护 DB。", "source": null, "related": [] }, { "id": "sa-008", "type": "short_answer", "difficulty": 2, "tags": [ "缓存穿透", "布隆过滤器" ], "question": "既然布隆过滤器有误判可能,为什么仍可用于防缓存穿透?", "answer": "因为布隆过滤器只存在假阳性(判断存在可能不存在),而不存在假阴性(判断不存在则一定不存在)。防穿透时我们最需要的是把「不存在的 key」挡在 DB 之前,对这类 key 布隆过滤器会给出确定的「不存在」结论从而直接拦截,不会漏放;即使有极少数误判为「存在」的空 key 放行,也会被后续空值缓存等兜底。误判只可能多放、不会漏放的不存在请求,所以可用。", "keywords": [ "无假阴性", "不存在一定拦截", "假阳性可接受", "空值兜底" ], "scoring_rubric": "说明无假阴性(不存在→一定不存在)得 40 分;解释用它拦截不存在的 key 得 30 分;解释误判为正存在可被兜底得 30 分。", "explanation": "关键是方向:布隆过滤器对「不存在」的结论是 100% 可靠,而防穿透恰好需要拦截人多不存在数据不被穿透。", "source": null, "related": [] }, { "id": "sa-009", "type": "short_answer", "difficulty": 4, "tags": [ "缓存空对象", "缓存穿透" ], "question": "缓存空对象方案解决了穿透,但可能带来什么新问题?如何缓解?", "answer": "新问题:大量【不同的不存在 key】都会被缓存为空对象,而这些键永远占着内存且无法被正常命中,长期积累会挤满缓存内存、浪费空间。缓解:1) 给空对象设置极短 TTL(如 5~10 分钟)让其及时失效;2) 结合布隆过滤器在入口先拦截明显不存在的 key,减少写入空缓存的数量;3) 做好内存淘汰与监控。", "keywords": [ "占满内存", "多空key", "极短TTL", "布隆过滤器拦截", "内存告避免" ], "scoring_rubric": "准确指出内存被大量空 key 占用得 40 分;给出短 TTL 缓解得 30 分;给出布隆过滤器/内存控制补充方案得 30 分。", "explanation": "空值缓存是「空间换时间」,代价是空间,所以必须用短 TTL + 前置拦截控制空 key 数量。", "source": null, "related": [] }, { "id": "sa-010", "type": "short_answer", "difficulty": 5, "tags": [ "多级缓存", "Redis高可用", "缓存雪崩" ], "question": "结合多级缓存思路,说明在缓存雪崩/宕机风险下如何设计一套层层兜底的缓存架构。", "answer": "可设计三层:最前一层用本地进程内缓存(Caffeine 类),承接热读取吞吐;中间层用 Redis 分布式缓存,主从+哨兵/集群保证高可用,避免宕机;最后层是数据库。访问顺序本地→Redis→DB,每层 miss 才下沉到下一层。配合机制:TTL 加随机打散集体过期;对 DB 再加限流、熔断、降级(如降级返回旧值或默认数据)。即使 Redis 瞬时不可用,本地缓存仍能扛住大部分读请求,DB 请求被限流保护,避免整体雪崩。", "keywords": [ "本地缓存", "Redis", "数据库", "层层兜底", "高可用", "限流熔断降级" ], "scoring_rubric": "给出本地+分布式+DB 分层架构得 40 分;说明访问顺序与每层兜底得 30 分;结合 TTL随机、限流降级、高可用写出 30 分。", "explanation": "多级缓存的本质是「冗余缓存层 + 兜底限流」,即使上层失效也由下层承接或降级,从架构上消除单点雪崩。", "source": null, "related": [] } ] }