在Redis面试中,面试题:如何解决 Redis 缓存穿透问题直接考察候选人的系统防护能力。一个专业的解决方案需要构建"前置拦截-缓存兜底-流量控制"的立体防御,这正是鳄鱼java在金融风控系统中实现无效请求拦截率达99.9%的实战经验。本文将通过"问题本质-技术方案-实战案例-面试话术"四步法,详解布隆过滤器、空值缓存等核心技术,助你在面试中展现分布式系统的安全设计思维。
一、缓存穿透的本质与业务危害

缓存穿透是指查询一个缓存和数据库中都不存在的数据,导致所有请求直接穿透到数据库,造成数据库压力骤增的现象。鳄鱼java技术团队总结其三大特征:
1. 请求特征
- 针对不存在的Key进行高频查询(如用户ID=-1、订单号=999999)
- 恶意攻击时QPS可达正常流量的10-100倍
- 传统缓存策略完全失效(Cache-Aside模式下不会缓存空结果)
2. 业务影响
- 数据库连接池耗尽,正常业务查询被阻塞
- CPU/IO资源被无效请求占用,响应时间从200ms增至5s
- 严重时导致数据库宕机,引发系统级联故障
某电商平台曾遭遇缓存穿透攻击,30分钟内收到1200万次无效商品ID查询,导致MySQL主库CPU飙升至100%,订单系统瘫痪。
3. 与缓存击穿/雪崩的区别
| 问题类型 | 核心原因 | 影响范围 | 数据状态 |
|---------|---------|---------|---------|
| 缓存穿透 | 查询不存在的数据 | 数据库压力 | 缓存/DB均无数据 |
| 缓存击穿 | 热点Key过期 | 单个Key的数据库压力 | DB有数据,缓存过期 |
| 缓存雪崩 | 大量Key同时过期或Redis宕机 | 整体数据库压力 | 缓存大面积失效 |
二、第一层防御:接口层参数校验
解决面试题:如何解决 Redis 缓存穿透问题,首先要在流量入口建立第一道防线。鳄鱼java推荐实施以下校验策略:
1. 合法性校验
- ID规则校验:用户ID必须为正整数(过滤负数/字符串ID)
- 业务规则校验:订单号必须符合正则表达式(如^ORD\d{10}$)
- 白名单校验:地域编码、商品分类等有限枚举值采用白名单过滤
某支付系统通过参数校验,将90%的恶意请求拦截在接入层,数据库压力降低60%。
2. 限流熔断
- IP限流:单IP每分钟最多60次请求(使用Redis INCR+EXPIRE实现)
- 用户限流:未登录用户限制更严格(如10次/分钟)
- 熔断降级:当无效请求占比>30%时,自动开启5分钟熔断
代码示例(基于Redis的IP限流):
String ip = request.getRemoteAddr();
String key = "ratelimit:ip:" + ip;
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}
if (count > 60) {
return ResponseEntity.status(429).body("请求过于频繁");
}
三、第二层防御:缓存空值与布隆过滤器
当接口层防御被突破后,需要在缓存层建立第二道防线。鳄鱼java详解两种核心技术:
1. 缓存空值策略
- 实现逻辑:当数据库查询结果为空时,缓存空值(如""、null)并设置短期TTL(5-15分钟)
- 代码示例:
String key = "product:" + productId;
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value.equals("NULL") ? null : JSON.parseObject(value, Product.class);
}
// 缓存未命中,查询数据库
Product product = productMapper.selectById(productId);
if (product == null) {
// 缓存空值,设置5分钟过期
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
return null;
}
// 缓存真实数据,设置1小时过期
redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 1, TimeUnit.HOURS);
return product;
- 优缺点:实现简单但可能浪费缓存空间,适合Key值有限的场景
2. 布隆过滤器(Bloom Filter)
- 原理:通过多个哈希函数将存在的Key映射到位数组,判断Key是否"一定不存在"或"可能存在"
- 实现方案:
- Redis Bloom模块:使用Redis官方BloomFilter模块(Redis 4.0+支持)
# 添加元素 BF.ADD product_filter 1001 # 判断元素是否存在 BF.EXISTS product_filter 1001 # 返回1存在,0不存在- 本地布隆过滤器:使用Guava BloomFilter(适合单机场景)
// 初始化:预计元素100万,误判率0.01 BloomFilter- 误判率控制:通过增大位数组大小和哈希函数数量降低误判率,鳄鱼java建议生产环境误判率控制在0.1%以内filter = BloomFilter.create( Funnels.longFunnel(), 1_000_000, 0.01); // 添加元素 filter.put(1001L); // 判断存在性 if (!filter.mightContain(productId)) { return null; // 一定不存在 }
3. 组合策略
鳄鱼java推荐"布隆过滤器+缓存空值"双重防护:
- 布隆过滤器拦截99%的无效Key
- 缓存空值处理布隆过滤器的误判请求(约0.1%)
某电商平台实施后,数据库无效查询从10万QPS降至100 QPS以下。
四、第三层防御:数据预热与动态加载
预防缓存穿透的核心是确保有效Key尽可能被缓存。鳄鱼java分享两种主动防御策略:
1. 缓存预热
- 全量预热:系统启动时加载核心数据(如热门商品、基础配置)
// 伪代码:启动时预热
@Component
public class CachePreloader implements CommandLineRunner {
@Override
public void run(String... args) {
List hotProducts = productService.getHotProducts();
for (Product product : hotProducts) {
String key = "product:" + product.getId();
redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 24, TimeUnit.HOURS);
}
}
}
- 增量预热:通过定时任务加载新增数据(如每小时更新商品列表)
2. 动态布隆过滤器
- 当新增数据时,同步更新布隆过滤器
- 定期(如每天)重建布隆过滤器,避免误判率随数据量增加而上升
- 实现示例:使用Canal监听MySQL binlog,捕获新增数据并更新过滤器
五、第四层防御:数据库与业务层防护
即使缓存层被穿透,数据库仍需具备最后一道防线。鳄鱼java建议实施:
1. 数据库防护
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





