feat: add cache-system topic with 105 questions (穿透/击穿/雪崩, 读写策略, 架构选型)
Deploy Examination / deploy (push) Successful in 5s
Deploy Examination / deploy (push) Successful in 5s
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"topic": "cache-three-problems",
|
||||
"type": "code_reading",
|
||||
"schema_version": "1.0.0",
|
||||
"generated": "2026-09-05T00:00:00+08:00",
|
||||
"questions": [
|
||||
{
|
||||
"id": "cr-001",
|
||||
"type": "code_reading",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"缓存空对象"
|
||||
],
|
||||
"question": "阅读以下 Go 代码(空值缓存封装),回答子问题。",
|
||||
"code": "func GetUser(ctx context.Context, id string) (*User, error) {\n key := \"user:\" + id\n v, err := rdb.Get(ctx, key).Result()\n if err == nil {\n if v == \"__NULL__\" {\n return nil, nil // 命中空缓存,视为不存在\n }\n u := &User{}\n json.Unmarshal([]byte(v), u)\n return u, nil\n }\n user, qerr := db.QueryUser(id)\n if qerr != nil {\n return nil, qerr\n }\n if user == nil {\n rdb.Set(ctx, key, \"__NULL__\", 300*time.Second) // 空值缓存,短TTL\n return nil, nil\n }\n b, _ := json.Marshal(user)\n rdb.Set(ctx, key, b, time.Hour)\n return user, nil\n}",
|
||||
"language": "go",
|
||||
"sub_questions": [
|
||||
{
|
||||
"index": 1,
|
||||
"type": "single_choice",
|
||||
"question": "这段代码主要用于解决哪种缓存问题?",
|
||||
"options": {
|
||||
"A": "缓存击穿",
|
||||
"B": "缓存穿透",
|
||||
"C": "缓存雪崩",
|
||||
"D": "缓存一致性问题"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "代码把数据库查不到的结果(user==nil)也缓存为 __NULL__ 占位空值并设置较短 TTL,这正是「缓存空对象」防穿透的典型实现。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"type": "short_answer",
|
||||
"question": "为什么空值缓存要设置 60 秒的【短】TTL 而不是与正常数据同样长?",
|
||||
"answer": "因为空值占据缓存但永远不会被命中有价值数据,如果 TTL 过长,大量不同的不存在 key 会被长期缓存、挤占内存甚至写满缓存(缓存空间被空值污染)。用短 TTL 可让空值及时失效释放空间,同时仍能在 TTL 内挡住对同一不存在 key 的重复穿透。",
|
||||
"explanation": "空值缓存是「空间换时间」,空间代价必须通过短 TTL 控制,否则大量空格 key 会耗尽内存。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"type": "single_choice",
|
||||
"question": "若攻击者改用大量【不同】的不存在 ID 请求,这段空值缓存能彻底挡住穿透吗?",
|
||||
"options": {
|
||||
"A": "能,因为空值已缓存",
|
||||
"B": "不能,因为攻击者换新 ID 就产生新空 key,空缓存会积累并失效后再穿透",
|
||||
"C": "能,因为 5 分钟 TTL 足够",
|
||||
"D": "不能,且会使 DB 丢失全部数据"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "空值缓存只对「来过且不存在」的 key 有效。攻击者不断换新 ID,每次都产生新的空 key 再次穿透 DB,并会积累空值占用内存。因此需要结合布隆过滤器在入口拦截,才能挡住批量变化 ID 的穿透。"
|
||||
}
|
||||
],
|
||||
"explanation": "这段 Go 代码是缓存穿透「空值缓存」方案的完整封装:miss 后回源,DB 无结果则写短 TTL 的空值占位。注意空值的 TTL 与拦截面(单 key vs 多 key)的平衡。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "cr-002",
|
||||
"type": "code_reading",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"互斥锁",
|
||||
"SETNX"
|
||||
],
|
||||
"question": "阅读以下 Redis 命令片段(互斥锁防击穿),回答子问题。",
|
||||
"code": "==== 流程 1:加锁回源 ====\nSET lock:hotkey 1 NX EX 3\n# 锁 key=lock:hotkey, 值 1, 仅当 key 不存在时设置(NX), 3 秒过期(EX)\n# 返回 OK: 抢到锁 → 回源 DB 并回填缓存\n# 返回 nil: 未抢到锁 → 等待 10ms 后重试或返回缓存旧值\n\n==== 流程 2:释放锁 ====\nDEL lock:hotkey\n\n==== 备注 ====\n回填后删除锁, 让其它等待请求命中缓存",
|
||||
"language": "redis",
|
||||
"sub_questions": [
|
||||
{
|
||||
"index": 1,
|
||||
"type": "single_choice",
|
||||
"question": "SET lock:hotkey123 1 NX EX 200 中,NX 和 EX 的作用分别是?",
|
||||
"options": {
|
||||
"A": "NX 设置过期时间,EX 表示仅当不存在时设置",
|
||||
"B": "NX 表示仅当 key 不存在时才设置,EX 设置过期时间为 200 秒",
|
||||
"C": "NX 表示强制覆盖,EX 表示永久有效",
|
||||
"D": "NX 是加锁,EX 是释放锁"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "SET key value NX EX seconds:NX 表示仅当 key 不存在时才设置(用于互斥抢占),EX 200 设置 200 秒过期。返回 OK/nil 表示是否抢到锁。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"type": "short_answer",
|
||||
"question": "为什么该互斥锁方案能避免大量请求同时打数据库(防止击穿)?",
|
||||
"answer": "因为 NX 保证了在同一时刻只有一个请求能成功设置 lock:hotkey1 这个锁并进入回源流程,其余大量请求因 SET 返回 nil 而拿不到锁,只能等待重试或直接读缓存旧值。于是原本瞬间几万打 DB 的并发,被收敛为「一个回源 + 其余等待/命中缓存」,数据库压力被限流正常水平,从而避免被瞬时尖峰冲垮。",
|
||||
"explanation": "互斥锁的核心是「并发串行化一个回源」,用等待换取 DB 安全,是击穿最直接的防御。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"type": "single_choice",
|
||||
"question": "若回源过程中线程崩溃、锁没被 DEL 删除,会怎样?",
|
||||
"options": {
|
||||
"A": "永远无影响",
|
||||
"B": "锁会因 EX 200 秒过期自动失效,后续请求可重新抢锁",
|
||||
"C": "缓存的 key 也会被删除",
|
||||
"D": "数据库会报错"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "给锁设置过期时间 EX 200 是防止死锁的关键:即使线程崩溃没有执行 DEL,锁也会在 200 秒后自动释放,避免永久锁住导致后续请求长期等待。"
|
||||
}
|
||||
],
|
||||
"explanation": "这是一套基于 Redis SET NX EX 的分布式互斥锁:NX 保证互斥、EX 设置超时防死锁。通过把并发回源收敛为一个,有效解决单热点 key 过期导致的缓存击穿。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "cr-003",
|
||||
"type": "code_reading",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"逻辑过期"
|
||||
],
|
||||
"question": "阅读以下 Go 代码“逻辑过期”防击穿片段,回答子问题。",
|
||||
"code": "type CachedValue struct {\n Value interface{} `json:\"value\"`\n ExpireAt int64 `json:\"expire_at\"` // 逻辑过期时间(ms)\n}\n\nfunc Get(key string) interface{} {\n raw := rdb.Get(ctx, key).Val()\n var cv CachedValue\n json.Unmarshal([]{byte(raw)}, &cv)\n if time.Now().UnixMilli() < cv.ExpireAt {\n return cv.Value // 未到逻辑过期, 直接返回\n }\n // 已到逻辑过期 → 发起后台异步刷新\n go asyncRefresh(key)\n return cv.Value // 仍返回旧值, 保证可用\n}\n\nfunc asyncRefresh(key string) {\n fresh := db.Query(key)\n rdb.Set(key, marshal(CachedValue{value: fresh, expireAt: ...}))\n}",
|
||||
"language": "go",
|
||||
"sub_questions": [
|
||||
{
|
||||
"index": 1,
|
||||
"type": "single_choice",
|
||||
"question": "逻辑过期到达后,代码做了什么?",
|
||||
"options": {
|
||||
"A": "物理 DEL 删除 key,下次请求重新回源",
|
||||
"B": "不删 key,用 goroutine 异步刷新数据,并仍返回旧值",
|
||||
"C": "直接抛异常",
|
||||
"D": "把 key 永久保留不过期"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "逻辑过期到达后不删除缓存:go asyncRefresh 在后台异步刷新,同时 return cv.Value 仍返回旧值,保证数据持续可用并挡住并发瞬时冲击。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"type": "single_choice",
|
||||
"question": "这比“到期 del 后所有请求回源”最大的优势是什么?",
|
||||
"options": {
|
||||
"A": "DB 数据更准",
|
||||
"B": "在过期窗口内请求仍能命中缓存,不会在瞬间对 DB 形成大量并发冲击",
|
||||
"C": "减少网络带宽",
|
||||
"D": "缓存永远不过期所以更快"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "物理删除会让窗口期所有请求同时回源打 DB(击穿);逻辑过期把请求留在缓存命中,只有后台异步线程刷新 DB,从源头消除了并发尖峰。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"type": "short_answer",
|
||||
"question": "逻辑过期方案的已知代价是什么?在什么场景要谨慎使用?",
|
||||
"answer": "代价:在后台刷新完成前,客户端读取到的是「旧数据」,存在短暂的数据时效性问题。因此在要求强一致、对实时性要求极严格(如金融实时账本、超高并发一致性敏感的写入)场景中应谨慎使用,更适用于可以短暂容忍旧值的读多写少的热点数据场景。",
|
||||
"explanation": "逻辑过期是用「短暂旧数据」换「删除 DB」的并发安全,数据可用性强但一致性弱,需根据场景权衡。"
|
||||
}
|
||||
],
|
||||
"explanation": "逻辑过期是击穿的另一主流方案:value 内嵌逻辑过期时间,过期后不删 key,由后台异步线程刷新并继续返回旧值,实现吞吐与可用性的平衡。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "cr-004",
|
||||
"type": "code_reading",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"布隆过滤器"
|
||||
],
|
||||
"question": "阅读以下 Go 代码(布隆过滤器防穿透),回答子问题。",
|
||||
"code": "import (\n cachedow \"github.com/.../bloom\"\n)\n\nvar bf = bloom.New(1000000, 0.01) // 容量100万, 误判率≤1%\n// 启动时把全部已存在 userId 加入过滤器\nfor _, id := range loadAllUserIDs() {\n bf.Add(id)\n}\n\nfunc GetUser(id string) *User {\n // 先用布隆过滤器判断是否存在\n if !bf.MightContain(id) {\n return nil // 过滤器说“不存在”→ 直接拦截,不查DB\n }\n // 过滤器可能误判返回“存在”, 继续走缓存/DB\n if u := getCache(id); u != nil {\n return u\n }\n u := db.QueryUser(id) // 真正的回源\n setCache(id, u)\n return u\n}",
|
||||
"language": "go",
|
||||
"sub_questions": [
|
||||
{
|
||||
"index": 1,
|
||||
"type": "single_choice",
|
||||
"question": "当 bf.MightContain(id) 返回 false 时,说明什么?",
|
||||
"options": {
|
||||
"A": "id 一定在数据库中",
|
||||
"B": "id 可能不存在,也可能存在",
|
||||
"C": "id 一定不存在",
|
||||
"D": "布隆过滤器误判了"
|
||||
},
|
||||
"answer": "C",
|
||||
"explanation": "布隆过滤器返回 false 表示「一定不存在」(无假阴性),因此可直接拦截不查 DB,这是防穿透的关键。只有返回 true 才是「可能存在」。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"type": "short_answer",
|
||||
"question": "既然 MightContain 可能返回 true 把不存在的 id 漏放下去,为什么这样设计仍然安全?",
|
||||
"answer": "因为布隆过滤器只存在假阳性(不存在可能被判存在),但不存在假阴性(存在不会被判不存在)。对于防穿透,我们需要拦住「确定不存在的 key」,这类 key 会被 MightContain 精确拦截;即使少量不存在的 key 被误判为 possible、放行到 DB 查询,命中率也很低且可被空值缓存兜底,不会形成持续穿透。漏放的是「假阳性的空查询」,而不是被隐藏真实存在的数据,仍然在防穿透可接受的错误范围内。",
|
||||
"explanation": "关键在于误判只发生在「判存在」方向,而拦截的是「判不存在」,方向性正确决定了用于穿透防线的合理可靠。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"type": "single_choice",
|
||||
"question": "新增了一个 userId 并写入数据库后,如果只对新 id 发起请求而不重新构建布隆过滤器,会怎样?",
|
||||
"options": {
|
||||
"A": "过滤器会自动感知新 id,无需任何处理",
|
||||
"B": "由于新 id 未被 Add 进过滤器,MightContain 可能返回 false,导致本已存在的数据被误判为不存在而被直接拦截",
|
||||
"C": "数据库会自动把新 userId 加入布隆过滤器",
|
||||
"D": "布隆过滤器会直接报错"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "布隆过滤器只支持添加、不支持删除,且已建好的过滤器集合不会自动包含新增数据。新的 userId 未被 Add,MightContain 极可能返回 false(视为「不存在」)而被 GetUser 直接拦截,导致 DB 中真实存在的新数据无法被访问。因此数据新增后必须【重建/增量更新】布隆过滤器,这也是它不支持删除之外的另一运维注意点。"
|
||||
}
|
||||
],
|
||||
"explanation": "布隆过滤器作为穿透前置屏障:以一定假阳性为代价换得对「不存在数据」的零假阴性拦截。但对动态新增的数据需要重新构建过滤器,否则新 id 会被误判不存在。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "cr-005",
|
||||
"type": "code_reading",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"缓存雪崩",
|
||||
"TTL加随机",
|
||||
"多级缓存"
|
||||
],
|
||||
"question": "阅读以下 Redis/Go 片段(TTL 加随机防雪崩),回答子问题。",
|
||||
"code": "// Go: 写缓存时 TTL 加随机, 打散到期时刻\nfunc SetWithJitter(key string, val interface{}, ttl time.Duration) {\n jitter := time.Duration(rand.Int63n(300)) * time.Second // 0~300s 随机\n rdb.Set(ctx, key, val, ttl+jitter)\n}\n\n// 批量初始化热点 key 的调用(原来都用了相同 TTL 60s)\nSetWithJitter(\"hot:a\", userA, 60*time.Second)\nSetWithJitter(\"hot:b\", userB, 60*time.Second)\nSetWithJitter(\"hot:c\", userC, 60*time.Second)\nSetWithJitter(\"hot:d\", userD, 60*time.Second)",
|
||||
"language": "go",
|
||||
"sub_questions": [
|
||||
{
|
||||
"index": 1,
|
||||
"type": "single_choice",
|
||||
"question": "如果去掉 jitter(全部用 60 秒固定 TTL),这些 key 会怎样?",
|
||||
"options": {
|
||||
"A": "各自独立随机过期",
|
||||
"B": "约在同一时刻(60秒后)集体过期, 瞬间全部穿透DB, 触发缓存雪崩",
|
||||
"C": "永不过期",
|
||||
"D": "过期时间会互相影响自动错开"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "相同的 TTL 会让同一批同时写入的 key 在【同一时刻】集体失效,请求瞬间全落 DB,正是雪崩的典型成因之一。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"type": "single_choice",
|
||||
"question": "加随机 jitter 后,最终达到什么效果?",
|
||||
"options": {
|
||||
"A": "让 key 永不过期",
|
||||
"B": "把各 key 的到期时刻分散到一个时间区间内,避免在同一时刻大面积集体失效",
|
||||
"C": "提高缓存准确率",
|
||||
"D": "成批 key 反而更集中过期"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "TTL 加随机把到期时刻错开,使同一时刻并发失效的 key 数量显著下降,从而降低 DB 瞬时压力,是防雪崩最常用最直接的手段。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"type": "short_answer",
|
||||
"question": "除 TTL 加随机外,还可以用哪些手段进一步防御该批 key 的雪崩(至少写两种)?",
|
||||
"answer": "1) 多级缓存:增加本地进程内缓存层,Redis 失效时由本地缓存兜底,再有 DB 压力。2) 加锁/限流/熔断降级:对 DB 访问做限流,超限则熔断或降级返回旧值/默认值,护住 DB。3) Redis 高可用:主从 + 哨兵 + 集群,避免单机宕机;4) 关键可以让人工预热/异步预加载,错峰刷新。任写两种即可。",
|
||||
"explanation": "雪崩需多层防线叠加:打散 TTL 是一层,多级缓存兜底、限流降级、高可用是补充层,确保即使某层失效也不会全盘崩溃。"
|
||||
}
|
||||
],
|
||||
"explanation": "TTL 加随机是防雪崩的第一道防线:把集体到期打散为分布到期。但单有它还不够,需配合多级缓存、限流降级与 Redis 高可用共同筑牢防御体系。",
|
||||
"source": null,
|
||||
"related": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
{
|
||||
"topic": "cache-three-problems",
|
||||
"type": "fill_blank",
|
||||
"schema_version": "1.0.0",
|
||||
"generated": "2026-09-05T00:00:00+08:00",
|
||||
"questions": [
|
||||
{
|
||||
"id": "fb-001",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 1,
|
||||
"tags": [
|
||||
"缓存穿透"
|
||||
],
|
||||
"question": "缓存穿透是指查询一个数据库中也______的数据,导致每次请求都直达数据库。",
|
||||
"answer": [
|
||||
"不存在"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "缓存穿透的本质是查询的 key 在数据库中也【不存在】,缓存查不到、写不回,因此每次请求都要穿透到 DB。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-002",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 1,
|
||||
"tags": [
|
||||
"缓存击穿"
|
||||
],
|
||||
"question": "缓存击穿是______个热点 key 缓存【刚好过期】,大量请求同时打数据库,特点是单 key、高并发。",
|
||||
"answer": [
|
||||
"一",
|
||||
"单",
|
||||
"1",
|
||||
"一个"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "击穿针对的是「单个特别热门的 key」刚过期,与穿透(不存在)和雪崩(成批)都不同,关键词是「单」。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-003",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 1,
|
||||
"tags": [
|
||||
"缓存雪崩"
|
||||
],
|
||||
"question": "缓存雪崩指______大批 key 在同一时刻集体失效,或整台缓存服务宕机,瞬时请求全部穿透到数据库。",
|
||||
"answer": [
|
||||
"一批",
|
||||
"成批",
|
||||
"多个",
|
||||
"很多",
|
||||
"大量"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "雪崩的关键词是「多 key / 成批 / 集体失效」,与击穿的单 key 形成对比。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-004",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"缓存空对象"
|
||||
],
|
||||
"question": "用「缓存空对象」防穿透:DB 查不到时也缓存一个 null,并设置______(如 5 分钟)的 TTL。",
|
||||
"answer": [
|
||||
"极短",
|
||||
"很短",
|
||||
"较短",
|
||||
"短"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "空值必须用极短 TTL,防止大量不同空 key 长期占用内存导致缓存爆掉。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-005",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"布隆过滤器"
|
||||
],
|
||||
"question": "布隆过滤器判断「不存在」时数据一定不存在(无______),判断「存在」时却可能存在(有______),且不支持删除。",
|
||||
"answer": [
|
||||
"假阴性",
|
||||
"假阳性"
|
||||
],
|
||||
"answer_rule": "ordered",
|
||||
"explanation": "布隆过滤器无假阴性(说不存在就一定不存在),有假阳性(说存在却可能不存在)。空按题目顺序填写「假阴性、假阳性」,因此 answer_rule 用 ordered。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-006",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"互斥锁",
|
||||
"SETNX"
|
||||
],
|
||||
"question": "用互斥锁解决击穿时,常用 Redis 的______命令实现分布式锁,保证同时只有一个请求回源数据库并回填缓存。",
|
||||
"answer": [
|
||||
"SETNX",
|
||||
"setnx",
|
||||
"SET NX",
|
||||
"SETNXEX"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "SETNX(SET if Not eXists)是 Redis 实现分布式锁最常用的命令:抢到锁的请求才回源,其余等待或降级。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-007",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"逻辑过期"
|
||||
],
|
||||
"question": "「逻辑过期」方案不真正删除缓存,到期后由______异步线程去更新,保证数据持续可用。",
|
||||
"answer": [
|
||||
"后台",
|
||||
"异步",
|
||||
"后台异步",
|
||||
"独立"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "逻辑过期由后台异步线程在后台刷新缓存,从而挡住所拦截的并发瞬时冲击,保证数据持续可用。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-008",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"缓存雪崩",
|
||||
"TTL加随机"
|
||||
],
|
||||
"question": "给缓存 key 的过期时间加______值(如 60+random(0~300) 秒),可把到期时刻打散,避免一批 key 同时失效。",
|
||||
"answer": [
|
||||
"随机",
|
||||
"随机偏差",
|
||||
"随机数",
|
||||
"随机量"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "TTL 加随机值把各 key 的到期时间分散开,防止同一时刻集体失效的雪崩。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-009",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"多级缓存",
|
||||
"缓存雪崩"
|
||||
],
|
||||
"question": "多级缓存的访问顺序是:本地进程内缓存 → ______ → DB,层层兜底防雪崩。",
|
||||
"answer": [
|
||||
"Redis",
|
||||
"分布式缓存",
|
||||
"缓存层",
|
||||
"分布式缓存层"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "典型多级缓存顺序为本地缓存(进程内)→ Redis(分布式缓存层)→ DB,逐层兜底,减小某层失效带来的冲击。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "fb-010",
|
||||
"type": "fill_blank",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"Redis高可用",
|
||||
"缓存雪崩"
|
||||
],
|
||||
"question": "为避免整台缓存服务宕机引发雪崩,可采用______(主从+哨兵)或集群方案保证缓存层高可用。",
|
||||
"answer": [
|
||||
"主从",
|
||||
"主从复制",
|
||||
"哨兵",
|
||||
"主从哨兵",
|
||||
"高可用架构"
|
||||
],
|
||||
"answer_rule": "any",
|
||||
"explanation": "Redis 高可用通常由主从复制 + 哨兵(故障自动切换)+ 集群(分片)组成,避免整机宕机导致缓存雪崩。",
|
||||
"source": null,
|
||||
"related": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"slug": "cache-three-problems",
|
||||
"name": "缓存穿透 / 击穿 / 雪崩",
|
||||
"description": "缓存的三大经典问题:缓存穿透、缓存击穿、缓存雪崩的定义、区别与优化方案",
|
||||
"tags": [
|
||||
"Redis高可用",
|
||||
"SETNX",
|
||||
"TTL加随机",
|
||||
"互斥锁",
|
||||
"参数校验",
|
||||
"多级缓存",
|
||||
"布隆过滤器",
|
||||
"热点key",
|
||||
"缓存击穿",
|
||||
"缓存空对象",
|
||||
"缓存穿透",
|
||||
"缓存雪崩",
|
||||
"逻辑过期"
|
||||
],
|
||||
"difficulty_range": [
|
||||
1,
|
||||
5
|
||||
],
|
||||
"schema_version": "1.0.0",
|
||||
"updated": "2026-09-05",
|
||||
"question_files": [
|
||||
"single_choice",
|
||||
"fill_blank",
|
||||
"short_answer",
|
||||
"code_reading"
|
||||
],
|
||||
"stats": {
|
||||
"total": 35,
|
||||
"by_type": {
|
||||
"single_choice": 10,
|
||||
"fill_blank": 10,
|
||||
"short_answer": 10,
|
||||
"code_reading": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"topic": "cache-three-problems",
|
||||
"type": "single_choice",
|
||||
"schema_version": "1.0.0",
|
||||
"generated": "2026-09-05T00:00:00+08:00",
|
||||
"questions": [
|
||||
{
|
||||
"id": "sc-001",
|
||||
"type": "single_choice",
|
||||
"difficulty": 1,
|
||||
"tags": [
|
||||
"缓存穿透"
|
||||
],
|
||||
"question": "「缓存穿透」的本质与下列哪种情况最匹配?",
|
||||
"options": {
|
||||
"A": "查询数据库中存在但缓存中不存在的数据",
|
||||
"B": "查询数据库中也根本不存在的数据,每次请求都直达数据库",
|
||||
"C": "单个热点 key 刚好过期导致大量请求同时打数据库",
|
||||
"D": "大批量 key 在同一时刻集体失效"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "缓存穿透指查询一个数据库中也【不存在】的数据。缓存查不到、DB 也查不到,无法写回缓存,导致每一次请求都直达 DB。选项 A 属于正常缓存未命中可回源;C 是缓存击穿;D 是缓存雪崩。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-002",
|
||||
"type": "single_choice",
|
||||
"difficulty": 1,
|
||||
"tags": [
|
||||
"缓存雪崩"
|
||||
],
|
||||
"question": "「缓存雪崩」的典型特征是?",
|
||||
"options": {
|
||||
"A": "单个热点 key 过期导致的高并发尖峰",
|
||||
"B": "查询不存在的数据导致持续打到数据库",
|
||||
"C": "大批 key 同时过期或整台缓存服务宕机,瞬间请求全部穿透到数据库",
|
||||
"D": "布隆过滤器误判导致正常请求被拦截"
|
||||
},
|
||||
"answer": "C",
|
||||
"explanation": "缓存雪崩的特征是【多 key、集中失效、集体崩溃】:一大批 key 在同一时刻集体失效(如同期设置了相同过期时间),或整台缓存服务宕机,导致瞬间所有请求穿透到 DB。选项 A 是击穿,B 是穿透,D 是布隆过滤器的副作用。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-003",
|
||||
"type": "single_choice",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"热点key"
|
||||
],
|
||||
"question": "某个微博热搜 key 缓存恰好过期,瞬时大量请求同时回源打数据库,这属于哪种问题?",
|
||||
"options": {
|
||||
"A": "缓存穿透",
|
||||
"B": "缓存击穿",
|
||||
"C": "缓存雪崩",
|
||||
"D": "缓存一致性问题"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "该场景是【单个特别热门的 key】缓存过期,此刻大量请求同时来全部 miss、同时打 DB。特点是单 key、高并发、瞬时尖峰,正符合缓存击穿的定义。穿透针对不存在的 key,雪崩针对多 key 集体失效,一致性问题与本题无关。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-004",
|
||||
"type": "single_choice",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"布隆过滤器"
|
||||
],
|
||||
"question": "关于布隆过滤器判断数据「是否存在」,下列说法正确的是?",
|
||||
"options": {
|
||||
"A": "判断不存在时,数据可能实际存在",
|
||||
"B": "判断不存在时,数据一定不存在",
|
||||
"C": "判断存在时,数据一定存在",
|
||||
"D": "不支持删除,因此只能用于处理缓存击穿"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "布隆过滤器不存在假阴性:判断为「不存在」则一定不存在。存在假阳性:判断为「存在」时可能实际不存在。选项 C 错误(存在→可能存在),选项 A 描述的是假阳性而非不存在,选项 D 布隆过滤器是解决穿透的方案而非击穿。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-005",
|
||||
"type": "single_choice",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"缓存空对象"
|
||||
],
|
||||
"question": "使用「缓存空对象」方案解决缓存穿透时,下列说法错误的是?",
|
||||
"options": {
|
||||
"A": "数据库查不到时,也缓存一个 null 值并设置较短的 TTL",
|
||||
"B": "可以有效防止对同一个不存在 key 的反复穿透",
|
||||
"C": "大量不同的空 key 都会占满内存,存在内存被写满的风险",
|
||||
"D": "空值对象应该设置与正常数据相同甚至更长的 TTL"
|
||||
},
|
||||
"answer": "D",
|
||||
"explanation": "缓存空对象应设置【极短 TTL】(如 5 分钟),防止大量空 key 长期占用内存,而不是设置相同或更长的 TTL。A、B 正确描述了原理,C 正确指出了空值缓存的主要内存风险,故错误的选项是 D。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-006",
|
||||
"type": "single_choice",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"互斥锁",
|
||||
"SETNX"
|
||||
],
|
||||
"question": "使用 Redis SETNX 实现互斥锁解决缓存击穿时,只有抢到锁的请求才能回源数据库,它的核心作用是?",
|
||||
"options": {
|
||||
"A": "让所有请求都穿透到数据库以加快数据同步",
|
||||
"B": "保证只有一个线程去查库回填缓存,其余请求等待或降级",
|
||||
"C": "把锁上的请求直接丢弃以减少数据库压力",
|
||||
"D": "用于给缓存 key 设置统一的过期时间"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "互斥锁保证同一时刻只有一个请求能去查 DB 并回填缓存,其他请求阻塞等待或返回旧值,从而避免雪崩式地打 DB。A、C 与思路相悖,D 与过期时间无关。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-007",
|
||||
"type": "single_choice",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"缓存雪崩",
|
||||
"TTL加随机"
|
||||
],
|
||||
"question": "为防止缓存雪崩,给大量缓存 key 的 TTL 增加随机偏差(如 60+random(0~300) 秒)能达到什么效果?",
|
||||
"options": {
|
||||
"A": "让所有 key 在同时到期,保证数据最新",
|
||||
"B": "把到期时间打散,避免一批 key 在同一时刻集体失效",
|
||||
"C": "彻底消除缓存过期,让数据永不失效",
|
||||
"D": "减少缓存换取流量,降低服务器带宽消耗"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "给每个 key 的 TTL 加随机偏差可以把到期时刻分散开,避免一大批复集中在同一时刻失效触发雪崩。A、C 与目标相反,D 与带宽无关。加随机是雪崩最直接有效的兜底手段之一。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-008",
|
||||
"type": "single_choice",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"缓存击穿",
|
||||
"逻辑过期"
|
||||
],
|
||||
"question": "关于「逻辑过期」解决缓存击穿的方案,下列说法正确的是?",
|
||||
"options": {
|
||||
"A": "到逻辑过期时间就立即物理删除缓存里的 key",
|
||||
"B": "不真正删除缓存,缓存到期后由异步线程后台更新,数据保持持续可用",
|
||||
"C": "逻辑过期会增加 DB 的瞬时并发压力",
|
||||
"D": "逻辑过期是专门用来解决缓存穿透的方案"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "逻辑过期不删除 key,而是在 value 中记录逻辑过期时间,允许返回旧值或标记过期后由后台异步线程刷新,从而挡住并发的瞬时冲击。A 是物理丢弃,C 与 B 的异步刷新理念相悖,D 它是解决击穿的方案。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-009",
|
||||
"type": "single_choice",
|
||||
"difficulty": 5,
|
||||
"tags": [
|
||||
"缓存穿透",
|
||||
"参数校验",
|
||||
"布隆过滤器"
|
||||
],
|
||||
"question": "某系统收到大量针对「不存在 userId」的请求,为在【不合法参数被鉴权】之前、从入口处优先拦截绝大多数非法请求,最直接的做法是?",
|
||||
"options": {
|
||||
"A": "直接返回数据库全表数据给客户端",
|
||||
"B": "对所有请求做参数校验,在入口拦截非法参数与不存在数据",
|
||||
"C": "给数据库添加唯一约束",
|
||||
"D": "把所有请求都用 SETNX 加锁串行化"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "参数校验是穿透的第一道防线:在入口处拦截非法参数、明显不存在的请求,避免其打到 DB。A 会雪上加霜,C 是数据库层措施但不防穿透,D 的加锁是为击穿而设且无法区分合法不存在的 key。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "sc-010",
|
||||
"type": "single_choice",
|
||||
"difficulty": 5,
|
||||
"tags": [
|
||||
"多级缓存",
|
||||
"Redis高可用",
|
||||
"缓存雪崩"
|
||||
],
|
||||
"question": "以下属于「缓存雪崩」完整治理组合方案的是?",
|
||||
"options": {
|
||||
"A": "仅设置:对所有 key 采用永久不过期",
|
||||
"B": "过期时间加随机值 + 多级缓存(本地/Redis/DB 兜底)+ 限流熔断降级 + Redis 主从/集群高可用",
|
||||
"C": "只使用布隆过滤器拦截不存在的 key",
|
||||
"D": "只采用互斥锁让单 key 回源串行"
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "雪崩是多 key 集体失效 + 服务宕机的双重风险,治理需要组合:TTL 加随机打散失效时间、多级缓存层层兜底、加锁/限流/熔断降级保护 DB、Redis 主从哨兵集群保证高可用。A 永不失效既不现实也不能防宕机,C 针对穿透,D 针对单 key 击穿而非成批雪崩。",
|
||||
"source": null,
|
||||
"related": []
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user