Files
examination/topics/thumbup/cache-protection/single_choice.json
T

168 lines
11 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": "cache-protection",
"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": [
"cache-protection",
"bloom-filter"
],
"question": "BloomFilterManager 中 EXPECTED_INSERTIONS 设为 1,000,000,FPP 设为 0.01。这意味着当实际插入量远超 1,000,000 时,最可能出现什么后果?",
"options": {
"A": "布隆过滤器自动扩容,无任何影响",
"B": "false positive rate(误判率)显著上升,导致更多不存在的 key 被错误放行",
"C": "布隆过滤器抛出 IllegalStateException 异常",
"D": "insert 操作被静默丢弃,不影响查询结果"
},
"answer": "B",
"explanation": "Guava BloomFilter 创建后容量固定,不会自动扩容。当实际插入量远超 expectedInsertions 时,位数组的每个 bit 被置 1 的比例升高,查询时误判率(false positive rate)会远高于预期的 1%(0.01),导致大量不存在的 key 也能通过 mightContain 检查,削弱布隆过滤器对缓存穿透的拦截效果。选项 A 错误,BloomFilter 没有扩容机制;选项 C 错误,不会抛异常;选项 D 错误,put 操作不会被丢弃。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 2,
"tags": [
"cache-protection",
"bloom-filter"
],
"question": "关于 BloomFilter.mightContain() 方法的返回值语义,下列哪项描述是正确的?",
"options": {
"A": "返回 true 表示该元素一定存在",
"B": "返回 false 表示该元素一定不存在",
"C": "返回 true 表示该元素一定不存在",
"D": "无论返回 true 还是 false 都是不确定的"
},
"answer": "B",
"explanation": "布隆过滤器的核心特性:mightContain 返回 false 则元素一定不存在(无 false negative),返回 true 则元素可能存在也可能不存在(有 false positive)。因此 ThumbServiceImpl.hasThumb() 中判断 !bloomFilterManager.mightContain(userId, blogId) 为 true 时直接返回 false,是安全的。选项 A 错误,true 不是确定性结论;选项 C 错误,true 表示可能存在;选项 D 错误,false 是确定性结论。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 2,
"tags": [
"cache-protection",
"null-cache"
],
"question": "CacheManager 中 NULL_CACHE_TTL_SECONDS 设为 30 秒。这个设计的核心目的是什么?",
"options": {
"A": "防止缓存雪崩,让空值在 30 秒后自然失效",
"B": "防止缓存穿透,避免同一个不存在的 key 反复穿透到 Redis",
"C": "防止缓存击穿,保护热点 key 的本地缓存",
"D": "防止内存溢出,限制 nullValueCache 的最大条目数"
},
"answer": "B",
"explanation": "当 Redis 中某个 key 不存在时,nullValueCache 会在 30 秒内缓存 NULL_PLACEHOLDER,后续相同 key 的查询直接在空值短缓存层返回 null,不再穿透到 Redis。这是典型的「缓存穿透防护」手段——通过缓存空值来拦截对不存在数据的反复查询。选项 A 描述的是缓存雪崩场景(大量 key 同时过期);选项 C 是击穿场景(热点 key 过期);选项 D 虽然 maximumSize 确实能防止内存溢出,但不是 TTL 设置的直接目的。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 3,
"tags": [
"cache-protection",
"mutex-lock"
],
"question": "CacheManager.get() 方法中使用了 synchronized(lock) 加互斥锁。结合代码分析,这种互斥锁设计主要解决的是什么问题?",
"options": {
"A": "防止缓存穿透:拦截不存在的 key 访问 Redis",
"B": "防止缓存击穿:避免热点 key 过期瞬间大量请求同时打到 Redis",
"C": "防止缓存雪崩:保证同一时刻只有一个线程写入缓存",
"D": "防止数据库连接池耗尽:限制并发数据库查询数"
},
"answer": "B",
"explanation": "这是经典的「互斥锁防击穿」模式:当本地缓存未命中时,先用 computeIfAbsent 在 lockMap 中为该 key 创建一把锁对象,然后 synchronized 加锁。锁内再次 double-check 本地缓存(防止其他线程已经完成加载)。如果仍未命中则查 Redis。这样即使热点 key 同时过期,也只有一个线程能穿透到 Redis,其余线程在锁上等待,避免了「击穿」导致的 Redis/数据库压力骤增。选项 A 是 nullValueCache 的职责;选项 C 场景不对;选项 D 的目标对象不对。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 3,
"tags": [
"cache-protection",
"mutex-lock"
],
"question": "CacheManager.get() 中在 synchronized 块内部先做了一次 localCache.getIfPresent(compositeKey) 检查(double-check)。这一步的作用是?",
"options": {
"A": "减少锁竞争时间,因为 synchronized 块内可以更快获取本地缓存",
"B": "防止线程在等待锁期间,另一个线程已完成加载,导致重复查 Redis",
"C": "防止 nullValueCache 被多个线程并发写入导致数据不一致",
"D": "防止 hotKeyDetector 被多次 add 导致热点误判"
},
"answer": "B",
"explanation": "这是经典的 double-check locking 模式:线程 A 持锁加载数据完成后释放锁,线程 B(或 C、D…)获得锁后,如果不做 double-check 就直接查 Redis,会导致同一个 key 被重复加载。double-check 在锁内再次检查本地缓存,如果已被前一个线程加载好,直接返回,避免重复查询。选项 A 描述的是减少持锁时间,但不是 double-check 的核心目的;选项 C 不涉及 nullValueCache 的并发写入问题;选项 D 虽然有关联但不是主要目的。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 3,
"tags": [
"cache-protection",
"null-cache"
],
"question": "CacheManager 中 nullValueCache 的 maximumSize 设为 10000。如果攻击者用大量不存在的 key 发起请求,以下哪种情况最可能发生?",
"options": {
"A": "nullValueCache 持续增长直到 OOM,因为不存在的 key 总是不同",
"B": "Caffeine 使用 W-TinyLFU 策略自动淘汰最不常用的空值条目,内存不会无限增长",
"C": "新写入的空值条目直接覆盖最旧的空值条目,类似于 LRU",
"D": "nullValueCache 会抛出 IllegalStateException 并阻止后续写入"
},
"answer": "B",
"explanation": "Caffeine 的 maximumSize 采用 W-TinyLFU(Window Tiny Least Frequently Used)淘汰策略。当条目数接近 10000 时,会自动淘汰访问频率最低的条目,腾出空间给新条目。因此即使攻击者用大量不存在的不同 key 来请求,nullValueCache 的内存也不会无限增长,旧的低频空值条目会被淘汰。选项 A 错误,Caffeine 不会 OOM;选项 C 描述的是简单 FIFO/LRU,不是 Caffeine 的策略;选项 D 错误,Caffeine 不会抛异常。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 4,
"tags": [
"cache-protection",
"bloom-filter"
],
"question": "ThumbServiceImpl.hasThumb() 中布隆过滤器与 CacheManager 的协作关系是:先用布隆过滤器判断 key 是否可能存在,再用 CacheManager 查缓存。如果把这两层的顺序反转(先查缓存,再查布隆过滤器),以下哪种说法最准确?",
"options": {
"A": "功能完全等价,因为两层都是拦截,顺序不影响最终结果",
"B": "布隆过滤器层失去意义,因为 CacheManager 本身已有空值缓存和互斥锁来防穿透",
"C": "仍然有效,但布隆过滤器的拦截时机后移,无法在第一时间过滤不存在的 key,增加了对空值缓存的无效访问",
"D": "会导致功能错误,因为 CacheManager 返回的 null 无法被布隆过滤器识别"
},
"answer": "C",
"explanation": "原设计中布隆过滤器作为最前置的「粗筛」,能在 O(1) 时间内快速排除大量不存在的 key,减少对 CacheManager/Redis 的无效访问。如果反转顺序,所有请求(包括不存在的 key)都会先走 CacheManager 的多层查询链路(本地缓存 → nullValueCache → 锁 + Redis),只有在 CacheManager 返回非 null 时才走布隆过滤器。此时布隆过滤器的前置拦截价值大打折扣。选项 A 错误,两层职责不同不能简单等价;选项 B 说法过于绝对,布隆过滤器仍有价值;选项 D 不成立,null 本身就是一个合法的返回值。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 4,
"tags": [
"cache-protection",
"mutex-lock"
],
"question": "CacheManager 的 lockMap 使用 ConcurrentHashMap<String, Object>,且锁对象通过 computeIfAbsent 创建。以下哪种场景可能导致该互斥锁机制失效?",
"options": {
"A": "多个 JVM 实例部署时,每个实例的 lockMap 独立,不同实例的线程无法互斥",
"B": "Redis 集群模式下,不同 slot 的 key 命中不同 Redis 节点,导致锁失效",
"C": "Caffeine 本地缓存被驱逐时,lockMap 中对应的锁对象也被清除",
"D": "synchronized 关键字在高并发下会导致 CPU 100%,锁机制自动降级为无锁模式"
},
"answer": "A",
"explanation": "lockMap 是 JVM 进程内的 ConcurrentHashMap,锁对象仅在当前 JVM 实例内有效。在多实例部署场景下,实例 A 的线程持有 lockMap 中的锁不会阻塞实例 B 的线程,因此多个实例可能同时穿透到 Redis。这是单机互斥锁的固有局限,通常需要配合分布式锁(如 Redis SETNX)来解决。选项 B 描述的是 Redis 分片问题,与锁无关;选项 C 不成立,lockMap 和 localCache 是独立的数据结构;选项 D 荒谬,synchronized 不会自动降级。",
"source": null,
"related": []
}
]
}