郑州高并发场景实战:Redis缓存击穿问题的解决方案

2026-08-04 阅读 0 作者:500元建网站

问题背景

去年双十一期间,郑州二七广场附近一家商场的线上商城做促销活动。活动开始前5分钟,系统突然挂了。技术团队一看日志,数据库连接池被打满,MySQL的CPU飙到95%以上,大量请求超时。从监控上看,Redis的某个key过期后,瞬间有8000多个请求同时打到数据库上,典型的缓存击穿。

这个key存的是活动商品列表数据,设置了2小时的过期时间。恰好在前5分钟的时候过期了,而此刻正是用户涌入的高峰期。缓存一失效,所有请求直接穿透到数据库,MySQL扛不住就挂了。

第一层方案:热点数据预加载

最直接的解决办法是不要让热点key过期。我们改了策略:对活动类商品数据,不设置过期时间,改为通过后台定时任务每隔10分钟刷新一次。这样缓存里始终有数据,不会出现缓存失效导致的击穿。

具体实现上,用了一个Spring Boot的定时任务,每10分钟从数据库查询最新商品数据,更新到Redis中。同时加了一个手动刷新接口,运营人员可以在后台一键刷新缓存,方便活动配置后立即生效。

这个方案简单有效,但有一个限制:只适用于数据量不大且更新频率不高的场景。如果商品列表有几万条数据,每次全量刷新Redis的压力也不小。这家商场的活动商品最多200个,全量刷新耗时不到50ms,完全没有压力。

第二层方案:互斥锁防并发穿透

预加载解决了热点key过期的问题,但如果某个key意外失效了(比如Redis重启、手动删除),还是会有击穿风险。我们加了一层互斥锁保护。

逻辑是这样的:当缓存未命中时,先尝试获取一个分布式锁(用Redis的SETNX实现),获取成功的线程去数据库查询数据并写回缓存,获取失败的线程等待100ms后重试读缓存。这样即使缓存失效,也只有一个请求打到数据库,不会出现8000个请求同时穿透的情况。

代码实现上用了Redisson的RLock,设置了3秒超时时间。为什么选3秒?因为数据库查询加写回缓存的平均耗时是200ms,3秒足够覆盖异常情况。如果3秒还没查完,大概率是数据库本身出了问题,这时候重试也没意义。郑州高新区一个做秒杀系统的同行用过类似的方案,他把超时设成了1秒,结果遇到慢查询时锁提前释放,还是出现了少量穿透。3秒是个比较稳的选择。

第三层方案:布隆过滤器防缓存穿透

缓存击穿和缓存穿透是两个概念。击穿是热点key失效导致的,穿透是查询不存在的数据导致缓存和数据库都没有。比如有人恶意请求一个不存在的商品ID,每次请求都会穿透到数据库。

防穿透用布隆过滤器。系统启动时把所有有效的商品ID加载到布隆过滤器中,请求过来先过布隆过滤器检查,如果商品ID不存在直接返回404,不走缓存也不走数据库。布隆过滤器的误判率我们设的是0.01%,40000个商品ID占用的内存大约60KB,性能损耗可以忽略。

这里有个细节:新增商品时要同步更新布隆过滤器。我们在商品上架的接口里加了一行代码,把新商品ID写入布隆过滤器。删除商品时不从布隆过滤器中移除(布隆过滤器不支持删除),而是让缓存自然过期。虽然会有少量"幽灵商品"请求穿透到数据库,但量很小,影响可控。

方案效果

三重方案上线后,今年3月的促销活动做了压测。模拟10000 QPS持续5分钟,系统稳定运行,数据库CPU峰值35%,响应时间P99在200ms以内。对比去年双十一的惨状,改善非常明显。

总结一下三层方案:预加载防止热点key过期、互斥锁防止并发穿透、布隆过滤器防止恶意查询。这三层不需要全部上,根据业务场景选组合就行。普通的展示型网站第一层就够了,高并发电商场景建议三层都上。说白了缓存设计没有银弹,得根据实际流量和数据特征来做取舍。

电话咨询 微信咨询 在线咨询 返回顶部
xycx202108

微信扫码咨询

×