圆山中庸 2025-12-13 18:10 采纳率: 98.5%
浏览 0
已采纳

涩和尚导致的缓存穿透如何解决?

“涩和尚”是中文社区中对“缓存穿透”问题的一种戏称,源于音似“se shang”。在高并发系统中,当大量请求查询一个不存在的数据时,由于缓存未命中,请求直接打到数据库,导致数据库压力骤增,即“缓存穿透”。常见问题是:恶意攻击或无效ID频繁访问使缓存失效,数据库不堪重负。如何通过布隆过滤器、空值缓存或接口层校验有效拦截非法请求,成为解决“涩和尚”问题的关键技术挑战。
  • 写回答

1条回答 默认 最新

  • 猴子哈哈 2025-12-13 18:24
    关注

    深入解析“涩和尚”:缓存穿透的成因与系统级应对策略

    1. 什么是“涩和尚”?——从谐音梗到技术本质

    “涩和尚”是中文开发者社区中对缓存穿透(Cache Penetration)问题的一种戏称,源于“se shang”的音似。这一术语形象地描绘了在高并发场景下,大量请求绕过缓存、直接冲击数据库的现象。

    当客户端频繁查询一个根本不存在的数据ID时,由于该数据既不在缓存中,也不在数据库中,每次请求都会导致缓存未命中(Cache Miss),进而访问数据库。这种行为在极端情况下会引发数据库负载飙升,甚至服务崩溃。

    2. 缓存穿透的典型场景与危害分析

    • 恶意攻击:攻击者构造大量非法ID发起请求,探测系统边界或实施DoS攻击。
    • 爬虫误触:搜索引擎或第三方爬虫抓取已删除资源链接,持续触发无效查询。
    • 业务逻辑缺陷:前端传参未校验,用户输入错误ID导致后端频繁查库。
    • 缓存雪崩连带效应:大规模缓存失效后,叠加穿透问题,形成复合型故障。
    问题类型触发条件影响层级典型表现
    缓存穿透请求不存在的数据数据库层QPS突增,连接池耗尽
    缓存击穿热点key过期瞬间缓存+DB瞬时高并发查库
    缓存雪崩大批key同时过期全链路系统级响应延迟

    3. 解决方案一:布隆过滤器(Bloom Filter)前置拦截

    布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否可能存在于集合中。其核心优势在于以少量误判率为代价,实现海量数据的快速存在性判断。

    
    // 示例:使用Google Guava构建布隆过滤器
    BloomFilter<String> bloomFilter = BloomFilter.create(
        Funnels.stringFunnel(Charset.defaultCharset()),
        1_000_000,       // 预估元素数量
        0.01             // 误判率1%
    );
    
    // 写入阶段:加载所有合法ID
    bloomFilter.put("user:1001");
    bloomFilter.put("user:1002");
    
    // 查询前判断
    if (!bloomFilter.mightContain(userId)) {
        return Response.error("Invalid user ID"); // 直接拒绝
    }
    

    通过在Redis之前部署布隆过滤器,可在接入层或服务网关完成非法请求过滤,有效阻断90%以上的穿透流量。

    4. 解决方案二:空值缓存(Null Value Caching)策略

    对于确实不存在的数据,可将其结果以特殊标记(如null或占位对象)写入缓存,并设置较短的TTL(如5分钟),避免重复查询数据库。

    1. 接收请求,提取查询Key(如商品ID)
    2. 尝试从Redis获取数据
    3. 若返回null且缓存中存在__NULL__:productId_xxx标记,则直接返回空结果
    4. 若无缓存记录,则查数据库
    5. 若数据库无结果,写入SET __NULL__:productId_xxx 1 EX 300
    6. 返回空响应

    5. 解决方案三:接口层多重校验机制

    在应用入口处增加合法性检查,形成第一道防线:

    • ID格式校验(正则匹配、长度限制)
    • 业务规则约束(如订单ID必须大于10000)
    • 频率限制(单IP每秒最多10次未知ID请求)
    • 黑名单机制(自动封禁异常来源IP)
    graph TD A[客户端请求] --> B{ID格式合法?} B -- 否 --> C[返回参数错误] B -- 是 --> D{布隆过滤器判断} D -- 不存在 --> E[拒绝请求] D -- 存在/可能存 --> F[查询缓存] F -- 命中 --> G[返回数据] F -- 未命中 --> H[查数据库] H -- 找到 --> I[写缓存并返回] H -- 未找到 --> J[写空值缓存]
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 12月14日
  • 创建了问题 12月13日