面试官最爱问的Redis缓存三兄弟:雪崩、穿透、击穿,我用外卖订单场景给你讲明白

张开发
2026/4/17 18:26:18 15 分钟阅读

分享文章

面试官最爱问的Redis缓存三兄弟:雪崩、穿透、击穿,我用外卖订单场景给你讲明白
外卖系统高并发实战Redis缓存三兄弟的解决方案中午12点某外卖平台的订单量突然激增系统开始出现响应延迟。用户反复刷新页面却看到网络开小差的提示而商家后台则不断弹出订单查询失败的报警。这熟悉的场景背后往往隐藏着Redis缓存使用中的三大经典问题雪崩、穿透和击穿。作为后端开发者如果不能透彻理解这三种异常情况就像厨师不知道火候控制一样危险。1. 缓存雪崩当所有活动同时失效想象一下外卖平台正在举行全城五折日数万商家同时参与促销活动。技术团队为每个店铺的促销信息设置了缓存并统一设置为2小时过期。当缓存集体失效的那一刻数据库瞬间收到了超过平时50倍的查询请求——这就是典型的缓存雪崩。雪崩问题的核心在于大量缓存同时失效导致请求直接穿透到数据库。在外卖系统中这类问题常出现在店铺营业状态缓存所有店铺状态同时更新全局促销活动缓存区域运力配置缓存解决方案对比表方案类型具体实现适用场景优缺点过期时间随机化基础过期时间随机偏移量如300秒±随机60秒批量缓存初始化场景实现简单但无法应对缓存全量崩溃多级缓存LocalCacheRedisDB三级回源极端高并发场景架构复杂维护成本高热点数据永不过期异步线程定期更新缓存极热点数据如头部商家可能产生脏读需要版本控制// Spring Boot中实现随机过期时间的配置示例 Bean public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() { return (builder) - builder .withCacheConfiguration(shopStatus, RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(300 new Random().nextInt(120)))); }在实际项目中我们采用了组合方案对普通店铺使用随机过期时间对头部Top100商家采用多级缓存策略。某次大促期间这种混合方案成功将数据库QPS从峰值2万稳定控制在5000以下。关键提示雪崩防护的重点不是完全避免缓存失效而是控制失效的节奏和影响范围。就像高峰期的地铁限流需要错峰而不是阻断。2. 缓存穿透恶意请求的完美风暴凌晨3点监控系统突然报警显示数据库CPU使用率达到100%。排查发现有大量请求在查询不存在的店铺IDshop_999999。这就是缓存穿透——查询根本不存在的数据导致每次请求都直达数据库。外卖系统中的典型穿透场景恶意爬虫遍历店铺ID用户输入非法ID尝试获取数据已下架商品被持续请求防御方案四重奏入口校验在Controller层对参数进行强校验GetMapping(/shops/{id}) public ResponseEntityShopInfo getShopInfo(PathVariable Min(1) Long id) { // 业务逻辑 }布隆过滤器使用RedisBloom模块预先存储所有合法ID# 使用Redisson实现的布隆过滤器 RBloomFilterLong bloomFilter redisson.getBloomFilter(shopIds); bloomFilter.tryInit(1000000L, 0.03); if(!bloomFilter.contains(shopId)) { return Result.error(店铺不存在); }空值缓存对不存在的key也进行短时间缓存SET shop:999999 NULL EX 60风控系统识别异常请求模式并自动封禁在某次安全审计中我们发现一个脚本正在以每秒200次的频率扫描店铺ID。通过部署布隆过滤器空值缓存组合方案成功将这类无效请求的数据库穿透率降低到0.1%以下。3. 缓存击穿爆款菜品的秒杀危机晚上6点某网红餐厅的招牌菜品突然被1万用户同时点击恰巧该菜品缓存刚刚过期。瞬间爆发的请求全部落到数据库这就是缓存击穿——单个热点key失效引发的系统风暴。外卖业务中的击穿高发区爆款菜品详情页限时秒杀活动头部商家主页互斥锁实现方案对比实现方式代码示例适用场景注意事项Redis SETNXSET lock_key 1 EX 10 NX简单分布式场景需处理锁续期问题Redisson锁RLock lock redisson.getLock();复杂分布式系统支持可重入、看门狗机制本地锁RedissynchronizedRedis混合架构要注意本地锁的范围public Shop getShopWithMutex(Long id) { String cacheKey shop: id; // 尝试从缓存获取 Shop shop redisTemplate.opsForValue().get(cacheKey); if (shop ! null) { return shop; } // 获取分布式锁 String lockKey lock:shop: id; try { boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS); if (locked) { // 二次检查缓存 shop redisTemplate.opsForValue().get(cacheKey); if (shop ! null) { return shop; } // 查询数据库 shop shopMapper.selectById(id); redisTemplate.opsForValue().set(cacheKey, shop, 1, TimeUnit.HOURS); return shop; } else { // 未获取到锁短暂休眠后重试 Thread.sleep(100); return getShopWithMutex(id); } } finally { redisTemplate.delete(lockKey); } }在实战中我们发现单纯的互斥锁在高并发下会导致大量线程阻塞。最终方案是结合互斥锁与本地缓存将数据库查询QPS从峰值8000降至200左右。4. 复合防御外卖系统的缓存架构实践真实的业务场景往往同时存在雪崩、穿透和击穿风险。某外卖平台日均订单量突破300万时我们设计了多层次的防御体系缓存架构四层防护前端限流滑动窗口算法控制API调用频率limit_req_zone $binary_remote_addr zoneapi_limit:10m rate100r/s;中间层过滤布隆过滤器拦截非法ID空值缓存吸收无效请求热点识别自动预热缓存集群多AZ部署防止单点故障差异化过期策略熔断降级机制数据库保护读写分离连接池限流慢查询优化监控指标看板指标名称预警阈值监控频率应对措施缓存命中率90%每分钟检查热点key分布数据库QPS5000每秒触发自动限流慢查询比例1%每5分钟SQL优化缓存穿透率5%实时调整布隆过滤器这套体系在去年双11期间经受住了考验当时最高并发达到每秒3万订单而数据库负载始终保持在安全水位。一个有趣的发现约70%的缓存问题实际上源于不当的键设计比如没有区分冷热数据。

更多文章