RedisRedis缓存穿透实战:布隆过滤器与空值缓存双剑合璧

问题背景:缓存穿透的典型症状

某电商平台订单查询接口,日均调用量3000万次,Redis命中率长期低于80%。监控发现,当用户查询一个不存在的订单ID时(如恶意遍历或前端异常传参),请求会直接穿透Redis到达MySQL,导致数据库QPS峰值高达8000,平均响应时间从20ms恶化到1.2s。

这就是典型的缓存穿透:缓存和数据库都不存在的数据,每次请求都打到DB。本文用两个实战方案解决,并给出可验证的压测数据。

方案一:空值缓存(简单直接)

思路:对查询结果为空的数据也写入Redis,设置较短过期时间(如60秒),防止恶意流量反复穿透。

复制代码
// 查询订单(伪代码)
Order getOrder(String orderId) {
    Object cache = redis.get("order:" + orderId);
    if (cache != null) {
        if (cache instanceof EmptyValue) return null;
        return (Order) cache;
    }
    // 缓存未命中,查询DB
    Order order = db.query("SELECT * FROM orders WHERE id = ?", orderId);
    if (order == null) {
        // 空值缓存,60秒过期
        redis.set("order:" + orderId, new EmptyValue(), 60);
        return null;
    }
    redis.set("order:" + orderId, order, 3600);
    return order;
}

优点:实现简单,对已存在的业务代码侵入小。缺点:大量不存在的key会占满Redis内存,且过期时间窗口内仍可能被攻击(但已大幅缓解)。

方案二:布隆过滤器(前置拦截)

思路:在Redis前加一层布隆过滤器,存储所有合法订单ID。查询时先判断ID是否可能存在,若不存在则直接返回null,根本不查Redis和DB。

复制代码
// 初始化布隆过滤器(启动时加载全量订单ID)
BloomFilter<String> bloom = BloomFilter.create(
    Funnels.stringFunnel(StandardCharsets.UTF_8), 
    1000_0000,  // 预计元素量1000万
    0.01);       // 误判率1%

// 查询接口改造
Order getOrder(String orderId) {
    if (!bloom.mightContain(orderId)) {
        // 布隆过滤器判定不存在,直接返回
        return null;
    }
    // 可能存在的才走缓存/DB逻辑
    Object cache = redis.get("order:" + orderId);
    if (cache != null) return (Order) cache;
    Order order = db.query(...);
    if (order != null) redis.set("order:" + orderId, order, 3600);
    return order;
}

要点:

  • 布隆过滤器只增不删,订单ID不会失效(业务上订单永久有效)
  • 误判率设1%,内存占用约12MB(1000万元素),可接受
  • 启动时全量加载,可用离线任务生成快照

验证与性能对比

用JMeter压测,模拟100万请求,其中80%是合法ID,20%是不存在的恶意ID。

  1. 裸Redis+DB(无防护):QPS 4000,DB连接池打满,平均RT 850ms,错误率12%
  2. 空值缓存方案:QPS 12000,DB QPS降至2000,平均RT 45ms,错误率0%
  3. 布隆过滤器方案:QPS 18000,DB QPS仅800,平均RT 22ms,错误率0%

最终采用双方案结合:布隆过滤器拦截99%的非法请求,空值缓存兜底1%的误判请求,DB QPS稳定在500以下,接口RT稳定在20ms以内。

实战总结

缓存穿透不是高深技术,但选择方案要看业务场景:

  • 数据量小且固定:空值缓存足够
  • 数据量大且只增不减:布隆过滤器更省内存
  • 两者结合是生产环境标准配置

关键是用压测数据说话,而不是凭感觉优化。

相关推荐
ShineWinsu1 个月前
对于C++:布隆过滤器(bloomfilter)的详细解析
c++·面试·位图·位运算·布隆过滤器·海量数据·比特位
Steadfast_GG2 个月前
Redis典型应用 - 缓存,缓存更新策略,内存淘汰策略,缓存预热,缓存穿透,缓存雪崩,缓存击穿
redis·缓存穿透·缓存击穿·缓存雪崩·缓存预热·缓存更新策略·内存淘汰策略
至乐活着3 个月前
Redis缓存设计模式深度实战:击穿、穿透、雪崩及一致性终极方案
spring boot·redis·缓存穿透·缓存雪崩·缓存设计
我叫张小白。4 个月前
Redis的缓存雪崩、击穿、穿透和解决方案
数据结构·redis·fastapi·缓存穿透·缓存击穿·雪崩·热点key问题
庞轩px4 个月前
第三篇:缓存穿透、击穿、雪崩——从原理到解决方案
redis·缓存·缓存穿透·缓存击穿·缓存雪崩·布隆过滤器·互斥锁/逻辑过期
下次再写4 个月前
【Redis实战】深入理解Redis缓存策略:从原理到Spring Boot实践
java·spring boot·redis·缓存穿透·缓存击穿·分布式缓存·缓存策略
Thanks_ks5 个月前
穿透海量数据的迷雾:深入理解布隆过滤器的架构哲学与工程权衡
redis·高并发·缓存穿透·架构设计·布隆过滤器·分布式系统·海量数据
Micro麦可乐5 个月前
Redis只会用来做缓存?解锁Redis非缓存的九个应用场景,90%程序员不知道的隐藏技能
数据库·redis·缓存·消息队列·分布式锁·延迟队列·布隆过滤器
__土块__5 个月前
Java 大厂一面模拟:从线程中断到缓存穿透的分布式链路拷问
线程池·缓存穿透·线程中断·java面试·布隆过滤器·消息幂等·大厂一面