目录导读

- 缓存穿透——空值缓存与布隆过滤器
- 缓存雪崩——过期时间抖动与多级缓存
- 热点Key重建——分布式锁与逻辑过期
- 排行榜实时更新——Zset的原子操作
- 大规模数据对账——Pipeline管道与Lua脚本
- 高频问答Q&A
在互联网高并发场景下,Redis凭借其极致的单线程IO模型与丰富的数据结构,成为扛住流量洪峰的“中流砥柱”,工具越强,用错之后的破坏力越大,本文基于线上环境的五个真实故障与优化案例,深度剖析Redis在生产中的“惊险一跃”,并给出可直接落地的解决方案。
缓存穿透——空值缓存与布隆过滤器 故障场景:某电商平台的商品详情接口,遭到恶意攻击者连续请求数据库中不存在的商品ID(如-1、99999999),由于缓存中无该数据,所有请求绕过Redis直接压向MySQL,导致数据库CPU瞬间飙升至100%,连接数耗尽。 根因分析:缓存无法命中时,未对“不存在”的结果做标记,导致每一次非法请求都穿透到存储层。 解决方案:双重防御,第一层:在接口层使用布隆过滤器(Bloom Filter),将所有合法商品ID预先写入bitmap(位图),O(1)时间内拦截99%以上的非法Key,第二层:对查询结果为null的数据,在Redis中写入一个短TTL(如60秒)的空值占位符,防止同一非法Key反复穿透。 效果数据:布隆过滤器拦截后,MySQL QPS从12000降至不足300,CPU恢复正常。
缓存雪崩——过期时间抖动与多级缓存
故障场景:某社交App的“热门Feed流”缓存,统一设置了凌晨2点整过期,到期瞬间,大量请求同时回源数据库,导致主库写入延迟放大,进而引起全站接口不可用。
根因分析:大批量Key在同一时间过期,如同“定时炸弹”集体引爆。
解决方案:过期时间加随机抖动,在设置TTL时,使用 baseTTL + random(0, 300) 秒,将集中过期打散成平缓的波峰,同时引入多级缓存架构(本地Caffeine + Redis),当Redis未命中时,先查本地缓存(过期时间更长),极大降低穿透比例。
避坑提示:不要依赖单一的缓存层,给数据套上“多层保险”。
热点Key重建——分布式锁与逻辑过期 故障场景:某明星官宣恋情,其微博点赞数Key在1秒内被读取100万次,该Key在缓存失效的瞬间,大量线程同时执行查询DB并重建缓存的操作,导致数据库查询重复放大10倍。 根因分析:高并发下对单一Key的“缓存击穿”。 解决方案:采用分布式锁(Redisson) 控制重建过程——只允许一个线程去查询DB并写回缓存,其它线程短暂自旋等待,更高级做法是逻辑过期:缓存不设物理TTL,而是在Value中存一个过期时间戳;当发现逻辑过期时,异步线程去刷新数据,旧值先行返回。 效果数据:数据库重复查询量从100万次骤降至1次,接口RT(响应时间)稳定在5ms以内。
排行榜实时更新——Zset的原子操作
业务需求:直播平台需要实时维护“礼物贡献榜”前100名。
实现方案:使用Redis的Sorted Set(有序集合),以用户ID为member(成员),贡献值为score(分数),每次送礼时,直接执行 INCRBY score 命令,查询Top100时,使用 ZREVRANGE key 0 99 WITHSCORES,单次IO返回整体榜单。
优化细节:为了避免大Key(几百万成员)造成的查询阻塞,在Redis侧做分段拆分(如按频道ID分片),利用Zset的 ZREMRANGEBYRANK 命令定期清理尾部用户,控制集合大小。
大规模数据对账——Pipeline管道与Lua脚本
业务场景:每日凌晨需要对前一天的订单余额与Redis中的积分进行对账。
性能痛点:如果逐条 GET 再 SET,10万条数据需要10万次网络RTT(往返时延),耗时超过5分钟。
解决方案:使用 Pipeline(管道) 一次性打包发送所有命令,减少RTT至1次,对于需要“先比较后更新”的原子性操作(如防止积分扣成负数),使用 Lua脚本 在服务端一次性执行判断与运算,保证原子性且避免多次网络交互。
避坑指南:Pipeline虽快,但无事务保障,若中间命令失败,需记录失败ID并做重试补偿。
高频问答Q&A
Q1:布隆过滤器是否万无一失? A:并非绝对可靠,它有理论上的误判率(说存在但实际不存在),但不会漏判(说不存在就一定不存在),设置合理的位数组长度(约为数据量的10倍)和哈希函数个数(约7个),可将误判率控制在1%以下。
Q2:分布式锁与SETNX有什么区别?
A:简单的 SETNX key value EX 10 容易因业务超时导致锁提前释放,引发并发问题,生产环境建议使用Redisson的看门狗机制,它会在业务未结束时自动续期锁的过期时间。
Q3:什么时候该用Lua脚本? A:当需要一次性执行“多条命令且要求原子性”时(例如扣减库存后判断是否小于0,并同时记录操作日志),Lua脚本在Redis中是原子执行的,并且避免了多次网络请求带来的性能损耗。
Q4:多级缓存中数据一致性怎么解决? A:采用“失效通知+最终一致”策略,当DB修改时,主动删除Redis中的Key;当Redis删除时,通过消息队列通知本地缓存失效,若短暂不一致,依赖本地缓存的短过期时间(如1分钟)兜底。
Redis的性能上限极高,但坑也极深,以上五个案例覆盖了穿透、雪崩、击穿、热点和大批量操作等高危场景,核心心法只有两点:一是让请求尽量在缓存层终止,不触碰DB;二是让重操作(重建缓存)尽量只做一次,建议在压测环境中先模拟上述故障,验证降级方案的可靠性,方能在大促期间稳坐钓鱼台。