多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Redis缓存三大问题:穿透、击穿与雪崩解决方案

Redis缓存三大问题:穿透、击穿与雪崩解决方案 1. Redis缓存三大经典问题解析作为后端工程师我们几乎每天都在和Redis打交道。但真正能把缓存用好的团队并不多见——去年我们系统就曾因为缓存问题导致整个服务雪崩直接损失了当天的全部订单。今天我就用最直白的语言结合真实案例拆解Redis最致命的三个缓存问题穿透、击穿和雪崩。这三个问题本质上都是缓存失效引发的连锁反应但触发机制和解决方案截然不同。理解它们的区别就像医生要分清感冒、流感和肺炎一样重要。下面我会用电商系统的实际场景带你看透每种问题的特征和应对方案。2. 缓存穿透无中生有的攻击2.1 什么是缓存穿透想象你开了一家超市每天都有陌生人拿着不存在的商品编号来问价。每次你都不得不翻遍整个仓库最后告诉对方没有这个商品。这就是缓存穿透——大量请求根本不存在的key导致请求直接打到数据库。我们电商系统就遇到过这种情况攻击者用脚本批量请求不存在的商品ID如-10这类非法ID导致MySQL的CPU直接飙到100%。更可怕的是这种攻击成本极低一个简单的Python脚本就能发起每秒上万次的请求。2.2 解决方案实测方案一布隆过滤器推荐我们在Redis前加装了布隆过滤器就像超市门口的自动查询机。现在所有请求先经过这个查询机检查# 初始化布隆过滤器 from pybloom_live import ScalableBloomFilter bf ScalableBloomFilter(initial_capacity1000000, error_rate0.001) # 预热合法商品ID for id in valid_goods_ids: bf.add(id) # 查询拦截 if not request_id in bf: return 商品不存在 # 直接拦截非法请求实测下来这个方案拦截了99.9%的非法请求数据库负载立降80%。布隆过滤器的特点是空间效率极高1百万数据只需约1.7MB内存。方案二缓存空对象对于确实可能存在的key如新上架商品我们采用缓存空对象策略# 设置空值缓存5分钟过期 SET goods:-1 NULL EX 300注意要设置较短的TTL避免长期占用内存。我们曾因为设置成1天导致Redis内存爆满。重要提示这两种方案可以组合使用。先用布隆过滤器拦截明确非法的ID再用空缓存处理可能的合法请求。3. 缓存击穿热点数据的猝死3.1 问题本质去年双11我们的爆款商品页面突然全部超时。查日志发现某个访问量10w/秒的商品缓存过期瞬间所有请求直接涌向数据库就像春运时地铁闸机突然全部打开。这就是缓存击穿——某个热点key过期时大量请求同时穿透到数据库。与穿透不同击穿针对的是真实存在但过期的热点数据。3.2 解决方案对比方案一互斥锁分布式锁我们最初用Redis的SETNX实现锁def get_data(key): data redis.get(key) if data is None: if redis.setnx(lock:key, 1): # 获取锁 try: data db.query(key) redis.set(key, data, ex3600) finally: redis.delete(lock:key) else: time.sleep(0.1) # 等待重试 return get_data(key) return data但实测发现当并发量极大时这种方案会导致大量请求堆积。后来我们改用Redisson的分布式锁性能提升明显。方案二逻辑过期更优雅的方案是采用逻辑过期实际缓存永不过期但值里包含过期时间{ value: 真实数据, expire: 1672531200 // Unix时间戳 }当发现数据逻辑过期时异步更新缓存。这个方案彻底避免了请求堆积但实现复杂度较高。方案三多级缓存我们现在采用本地缓存Redis的二级架构先从本地缓存Caffeine读取本地没有则查RedisRedis没有才查数据库本地缓存设置不同的过期时间如随机5-7分钟避免同时失效。这个方案将数据库QPS降低了两个数量级。4. 缓存雪崩集体阵亡的灾难4.1 雪崩现象我们的监控系统曾记录到这样一次故障凌晨3点Redis集群大量key同时过期导致数据库瞬时QPS从200飙升到8万整个系统在15秒内完全瘫痪。雪崩比击穿更可怕之处在于大量key同时失效导致数据库承受指数级压力。常见原因包括缓存服务器宕机相同TTL导致同时过期缓存预热不充分4.2 防御方案方案一差异化过期时间现在我们设置缓存过期时间时会加上随机值# 基础过期时间随机偏移1小时内 expire_time 3600 random.randint(0, 3600) redis.set(key, value, exexpire_time)这个小技巧让key的过期时间均匀分布避免集中失效。方案二熔断降级我们在系统接入层实现了熔断机制监控数据库QPS超过阈值时自动返回降级内容如推荐商品列表同时触发缓存紧急预热方案三高可用架构现在我们的缓存架构是这样的多机房部署主从哨兵模式热点数据多副本离线备份缓存数据这套架构在今年618期间成功扛住了每秒50万次的查询量。5. 实战中的经验教训5.1 监控指标清单这些指标必须设置报警缓存命中率低于80%要预警数据库QPS突增Redis内存使用率慢查询数量5.2 常见配置误区我们踩过的坑布隆过滤器误判率设置过高导致正常请求被拦截分布式锁未设置超时死锁导致服务不可用本地缓存未限制大小OOM杀死服务5.3 性能对比数据三种解决方案的实测效果单节点QPS方案无防护简单防护完整方案缓存穿透1200950028000缓存击穿8001500045000缓存雪崩系统崩溃500030000最后分享一个检查清单每次上线前确认是否为热点key设置了永不过期或长过期时间布隆过滤器已预热最新数据过期时间有随机偏移降级策略已配置生效记住缓存问题往往在流量高峰时爆发但解决方案必须平时就准备好。就像我们CTO常说的对待缓存要像对待你的银行账户——平时多存点关键时刻才够用。
返回列表