84 lines
10 KiB
JSON
84 lines
10 KiB
JSON
|
|
{
|
|||
|
|
"topic": "cache-protection",
|
|||
|
|
"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": [
|
|||
|
|
"cache-protection",
|
|||
|
|
"null-cache",
|
|||
|
|
"bloom-filter"
|
|||
|
|
],
|
|||
|
|
"question": "缓存穿透(Cache Penetration)是指查询一个数据库中也不存在的数据,导致请求每次都打到数据库。请结合提供的代码,说明这套系统中有哪些机制可以防止缓存穿透,并分析各机制的优缺点。",
|
|||
|
|
"answer": "系统中有两层防穿透机制:\n1. **布隆过滤器(BloomFilterManager)**:在最前置层快速判断 key 是否可能存在,false 则直接返回,减少对缓存层的无效访问。优点:O(1) 时间复杂度,内存占用极低。缺点:存在 false positive(误判),且不支持删除操作,容量固定不能动态扩容。\n2. **空值短缓存(nullValueCache)**:对 Redis 返回 null 的 key,缓存 NULL_PLACEHOLDER 30 秒。优点:实现简单,有效拦截同一 key 的短时间重复穿透。缺点:TTL 过长会浪费内存,过短则防穿透效果有限;不适用于攻击者使用大量随机 key 的场景(会被 Caffeine 淘汰)。\n\n两层互补:布隆过滤器拦截「一定不存在」的 key,nullValueCache 接住布隆过滤器 false positive 漏过的少量 key。",
|
|||
|
|
"keywords": [
|
|||
|
|
"布隆过滤器",
|
|||
|
|
"false positive",
|
|||
|
|
"O(1)",
|
|||
|
|
"空值缓存",
|
|||
|
|
"NULL_PLACEHOLDER",
|
|||
|
|
"TTL",
|
|||
|
|
"互补"
|
|||
|
|
],
|
|||
|
|
"scoring_rubric": "评分标准:准确描述布隆过滤器的防穿透原理(1.5分);准确描述 nullValueCache 的防穿透原理(1.5分);分析各机制的优缺点(1分);说明两层机制的互补关系(1分)",
|
|||
|
|
"source": null,
|
|||
|
|
"related": [],
|
|||
|
|
"explanation": "系统中有两层防穿透机制:\n1. **布隆过滤器(BloomFilterManager)**:在最前置层快速判断 key 是否可能存在,false 则直接返回,减少对缓存层的无效访问。优点:O(1) 时间复杂度,内存占用极低。缺点:存在 false positive(误判),且不支持删除操作,容量固定不能动态扩容。\n2. **空值短缓存(nullValueCache)**:对 Redis 返回 null 的 key,缓存 NULL_PLACEHOLDER 30 秒。优点:实现简单,有效拦截同一 key 的短时间重复穿透。缺点:TTL 过长会浪费内存,过短则防穿透效果有限;不适用于攻击者使用大量随机 key 的场景(会被 Caffeine 淘汰)。\n\n两层互补:布隆过滤器拦截「一定不存在」的 key,nullValueCache 接住布隆过滤器 false positive 漏过的少量 key。"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sa-002",
|
|||
|
|
"type": "short_answer",
|
|||
|
|
"difficulty": 4,
|
|||
|
|
"tags": [
|
|||
|
|
"cache-protection",
|
|||
|
|
"mutex-lock"
|
|||
|
|
],
|
|||
|
|
"question": "缓存击穿(Cache Breakdown)是指某个热点 key 在缓存过期的瞬间,大量并发请求同时穿透到后端。请结合 CacheManager.get() 的代码,详细解释互斥锁 + double-check 模式如何解决缓存击穿问题,并指出该方案在多实例部署下的局限性。",
|
|||
|
|
"answer": "互斥锁 + double-check 模式的防护流程:\n1. 线程 A 检测到本地缓存未命中,在 lockMap 中通过 computeIfAbsent 为该 key 创建锁对象。\n2. 线程 A 获得 synchronized 锁,执行 double-check(再次检查本地缓存)。\n3. double-check 未命中,线程 A 从 Redis 加载数据,写入本地缓存,释放锁。\n4. 线程 B(在 A 持锁期间等待)获得锁后执行 double-check,此时本地缓存已有数据,直接返回,不查 Redis。\n\n通过这种方式,即使热点 key 同时过期,也只有一个线程穿透到 Redis,其余线程在锁上等待后命中 double-check。\n\n**多实例部署局限性**:lockMap 是 JVM 内存中的 ConcurrentHashMap,锁对象仅在当前 JVM 实例内有效。多实例部署时,实例 A 和实例 B 的线程各自持有独立的锁,无法互斥,仍可能同时穿透到 Redis。需要引入分布式锁(如 Redis SETNX)来解决跨实例的互斥问题。",
|
|||
|
|
"keywords": [
|
|||
|
|
"互斥锁",
|
|||
|
|
"double-check",
|
|||
|
|
"本地锁",
|
|||
|
|
"computeIfAbsent",
|
|||
|
|
"热点key",
|
|||
|
|
"多实例局限性",
|
|||
|
|
"分布式锁"
|
|||
|
|
],
|
|||
|
|
"scoring_rubric": "评分标准:准确描述互斥锁的获取和释放流程(1.5分);解释 double-check 的作用和必要性(1.5分);指出多实例部署下本地锁的局限性(1分);提出分布式锁等替代方案(1分)",
|
|||
|
|
"source": null,
|
|||
|
|
"related": [],
|
|||
|
|
"explanation": "互斥锁 + double-check 模式的防护流程:\n1. 线程 A 检测到本地缓存未命中,在 lockMap 中通过 computeIfAbsent 为该 key 创建锁对象。\n2. 线程 A 获得 synchronized 锁,执行 double-check(再次检查本地缓存)。\n3. double-check 未命中,线程 A 从 Redis 加载数据,写入本地缓存,释放锁。\n4. 线程 B(在 A 持锁期间等待)获得锁后执行 double-check,此时本地缓存已有数据,直接返回,不查 Redis。\n\n通过这种方式,即使热点 key 同时过期,也只有一个线程穿透到 Redis,其余线程在锁上等待后命中 double-check。\n\n**多实例部署局限性**:lockMap 是 JVM 内存中的 ConcurrentHashMap,锁对象仅在当前 JVM 实例内有效。多实例部署时,实例 A 和实例 B 的线程各自持有独立的锁,无法互斥,仍可能同时穿透到 Redis。需要引入分布式锁(如 Redis SETNX)来解决跨实例的互斥问题。"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sa-003",
|
|||
|
|
"type": "short_answer",
|
|||
|
|
"difficulty": 5,
|
|||
|
|
"tags": [
|
|||
|
|
"cache-protection",
|
|||
|
|
"bloom-filter",
|
|||
|
|
"null-cache",
|
|||
|
|
"mutex-lock"
|
|||
|
|
],
|
|||
|
|
"question": "假设你需要将这套缓存穿透与击穿防护方案从单体应用迁移到微服务架构(10+ 实例),请分析现有方案中哪些组件需要改造,给出具体的改造方案,并评估每种改造的利弊。",
|
|||
|
|
"answer": "需要改造的组件及方案:\n\n1. **布隆过滤器 → 分布式布隆过滤器**\n - 现状:每个实例维护独立的 BloomFilter,数据不同步。\n - 方案:使用 Redis Bloom(RedisBloom 模块)或自建基于 Redis 的分布式布隆过滤器,所有实例共享同一个过滤器。\n - 利:数据一致,任意实例都能拦截不存在的 key。弊:引入 Redis 依赖,增加一次网络往返延迟。\n\n2. **本地锁 → 分布式锁**\n - 现状:lockMap 是 JVM 内存锁,无法跨实例互斥。\n - 方案:使用 Redis SETNX 或 Zookeeper 实现分布式锁替代 synchronized。\n - 利:真正实现跨实例互斥。弊:锁获取/释放需要网络通信,延迟增加;需要处理锁超时、续期等复杂场景。\n\n3. **nullValueCache → 共享空值缓存或保留本地**\n - 现状:每个实例独立维护空值缓存,可能导致多实例同时穿透。\n - 方案 A:将 nullValueCache 也存入 Redis(带短 TTL),实现跨实例共享。利:一致性好。弊:增加 Redis 读写压力。\n - 方案 B:保持本地 nullValueCache 不变,接受少量重复穿透。利:简单、无额外网络开销。弊:一致性略差。\n\n4. **本地缓存 → 分布式缓存或本地+二级缓存架构**\n - 需要评估是否保留本地缓存层,或使用 Caffeine + Redis 的二级缓存架构,通过消息总线(如 Redis Pub/Sub 或 Kafka)实现缓存失效通知。\n\n综合评估:改造的核心权衡是「一致性 vs 延迟/复杂度」。布隆过滤器和互斥锁建议优先改造为分布式方案,空值缓存可根据业务容忍度选择保留本地或共享。",
|
|||
|
|
"keywords": [
|
|||
|
|
"分布式布隆过滤器",
|
|||
|
|
"Redis Bloom",
|
|||
|
|
"分布式锁",
|
|||
|
|
"SETNX",
|
|||
|
|
"空值缓存共享",
|
|||
|
|
"二级缓存",
|
|||
|
|
"一致性",
|
|||
|
|
"延迟"
|
|||
|
|
],
|
|||
|
|
"scoring_rubric": "评分标准:准确识别布隆过滤器、本地锁、空值缓存三个需要改造的组件(1分);为布隆过滤器提出分布式方案(如 Redis Bloom)(1分);为互斥锁提出分布式锁方案(如 SETNX)(1分);为空值缓存提出两种可选方案并分析利弊(1分);整体分析有结构,权衡合理,结论清晰(1分)",
|
|||
|
|
"source": null,
|
|||
|
|
"related": [],
|
|||
|
|
"explanation": "需要改造的组件及方案:\n\n1. **布隆过滤器 → 分布式布隆过滤器**\n - 现状:每个实例维护独立的 BloomFilter,数据不同步。\n - 方案:使用 Redis Bloom(RedisBloom 模块)或自建基于 Redis 的分布式布隆过滤器,所有实例共享同一个过滤器。\n - 利:数据一致,任意实例都能拦截不存在的 key。弊:引入 Redis 依赖,增加一次网络往返延迟。\n\n2. **本地锁 → 分布式锁**\n - 现状:lockMap 是 JVM 内存锁,无法跨实例互斥。\n - 方案:使用 Redis SETNX 或 Zookeeper 实现分布式锁替代 synchronized。\n - 利:真正实现跨实例互斥。弊:锁获取/释放需要网络通信,延迟增加;需要处理锁超时、续期等复杂场景。\n\n3. **nullValueCache → 共享空值缓存或保留本地**\n - 现状:每个实例独立维护空值缓存,可能导致多实例同时穿透。\n - 方案 A:将 nullValueCache 也存入 Redis(带短 TTL),实现跨实例共享。利:一致性好。弊:增加 Redis 读写压力。\n - 方案 B:保持本地 nullValueCache 不变,接受少量重复穿透。利:简单、无额外网络开销。弊:一致性略差。\n\n4. **本地缓存 → 分布式缓存或本地+二级缓存架构**\n - 需要评估是否保留本地缓存层,或使用 Caffeine + Redis 的二级缓存架构,通过消息总线(如 Redis Pub/Sub 或 Kafka)实现缓存失效通知。\n\n综合评估:改造的核心权衡是「一致性 vs 延迟/复杂度」。布隆过滤器和互斥锁建议优先改造为分布式方案,空值缓存可根据业务容忍度选择保留本地或共享。"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|