大促期间缓存穿透不会直接击垮数据库,但它会让每一次本应被拦截的无效请求都直落数据库,在流量峰值时悄悄吃掉宝贵的数据库连接和磁盘IO,成为压垮系统的最后一根稻草。
为什么大促时缓存穿透的破坏力被成倍放大
平时缓存穿透来了,数据库扛得住,顶多慢查询多几条,但大促场景完全不同,流量是平日的数倍甚至数十倍,数据库的连接池、线程池、磁盘IO每一项资源都绷在极限边缘,这时候穿透请求混在正常流量里,就像高速公路上突然混入一批不按规则行驶的车辆,堵住的不是它们自己,而是后面所有正常请求的路。
业内专家指出,大部分缓存穿透问题的根源不在缓存层,而在入口层的参数校验和流量治理做得不够细,大促的流量特征让这个问题从“隐患”升级成“事故”,这是很多团队在复盘时才发现的事实。
缓存穿透在大促中的典型症状
- 数据库监控面板上QPS曲线出现尖刺状波动,与缓存命中率曲线呈镜像关系
- 大量请求打到数据库的
select where id = xxx,且这些id在缓存中完全不存在 - 数据库连接池活跃连接数持续高位,但业务实际成功量并没有同步增长
- 慢查询日志里出现大量单行查询,单条执行时间几十毫秒甚至上百毫秒
缓存穿透和缓存击穿的区别:大促场景下的两种“隐形杀手”
缓存穿透和缓存击穿经常被放在一起讨论,但它们在数据库侧造成的压力模型完全不同,打个比方,缓存穿透是外部攻击者或异常请求不断敲一扇根本不存在的门,而缓存击穿是热门数据缓存过期的那一瞬间,所有请求同时涌向数据库去重建缓存。
| 对比维度 | 缓存穿透 | 缓存击穿 |
|---|---|---|
| 请求特征 | 查询不存在的数据 | 查询同一个热点数据 |
| 数据库压力 | 分散但持续 | 瞬间集中爆发 |
| 大促放大效应 | 无效请求占比升高,挤占正常请求资源 | 热点商品一旦失效,流量全部砸向数据库 |
| 恢复难度 | 需要查源头,修复慢 | 重建缓存后自动恢复 |
| 解决思路 | 入口校验、缓存空值、布隆过滤器 | 互斥锁、逻辑过期、热点数据永不过期 |
大促时这两者经常叠加出现:某个热门商品正在秒杀,缓存刚好过期,同时又有大量不存在的商品id在被打扫,数据库在同一个时间窗口内承受双重压力,这也是为什么只做单一防护措施往往不够的原因。
大促缓存穿透怎么解决:三层面拦截的实操路径
缓存穿透的治理不是加一个布隆过滤器就完事,需要从入口到缓存再到数据库三个层面逐层拦截。
第一层:入口参数校验,过滤掉“一眼假”的请求
很多穿透请求其实在入口就能识别,比如商品id是负数、超过合理范围、格式不对,这些请求根本没有必要进入后续流程。
具体操作路径如下:
- 在API网关层配置参数校验规则,对接入参数做格式、范围、枚举值检查
- 对于明显异常的请求直接返回错误码,不进入业务逻辑
- 在大促前编写脚本批量测试异常id场景,确保校验逻辑真正生效
这一步能拦截掉一部分低质量的攻击流量,但真正恶意且精心构造的穿透请求(比如用连续不存在的合法id),单靠参数校验是挡不住的。
第二层:缓存空值,用Redis替数据库挨打
当数据库查询结果为空时,把空结果也缓存起来,设置一个较短的过期时间(比如3-5分钟),这是最直接也最有效的防线,实现成本很低。
// 伪代码示例
Object value = redis.get(key);
if (value == null) {
Object dbValue = db.query(key);
if (dbValue == null) {
// 缓存空值,防止穿透
redis.set(key, EMPTY_PLACEHOLDER, 300);
} else {
redis.set(key, dbValue);
}
return dbValue;
}
但这里有个大促特有的坑:如果攻击者用随机不存在的id循环请求,缓存空值会让Redis里塞满垃圾key,内存压力上升,同时Redis的过期清理策略也会增加额外开销,所以空值缓存需要配合以下策略使用:
- 空值key的过期时间要比正常数据短,让不存在的key尽快释放内存
- 对空值key做内存上限限制,比如空值缓存最多占Redis总内存的5%
- 在Redis的淘汰策略上,优先淘汰空值key而不是正常数据key
第三层:布隆过滤器,把不存在的key挡在缓存层之外
布隆过滤器的作用是在缓存之前再设一道闸门:将所有可能存在的id加入一个位数组中,请求进来时先判断这个id是否在集合中,如果不在,直接返回不存在,根本不会触及数据库。
大促前构建布隆过滤器的操作步骤:
- 从数据库中导出全量有效商品id
- 预估数据量和期望的误判率(通常设置在0.1%以下)
- 计算位数组大小和哈希函数个数,初始化布隆过滤器
- 将修改操作同步更新到布隆过滤器中,保证新商品也能正常被识别
布隆过滤器的误判率是可控的,但注意它不能删除元素,这意味着如果某个商品被下架了,布隆过滤器里仍然会有它的位标记,但这只可能导致请求进入缓存层后被空值缓存拦截,不会对数据库造成影响。
大促期间数据库侧的兜底与容量规划
无论前面做了多少层防护,都要假设穿透流量仍然会漏到数据库,因此数据库侧必须有兜底方案和容量冗余。
大促前数据库侧要做的事
- 对核心表做读写分离,读流量全部走从库,主库只处理写请求,这样穿透的读请求即使到达数据库,影响范围也局限在从库上
- 根据大促预估流量计算数据库连接池上限,预留30%-40%的余量应对突发情况
- 在数据库层配置限流和熔断规则,比如当某个接口的数据库耗时超过阈值时,直接丢弃部分请求
- 开启慢日志监控,大促期间如果发现大量单行查询的慢日志,说明穿透拦截没有完全生效
用数据说话:穿透对数据库压力的传导模型
假设大促期间每秒有10万个请求,正常情况下缓存命中率是95%,只有5000个请求落到数据库,但如果存在5%的穿透率,数据库每秒要额外承受5000个无效查询这相当于把数据库的负载直接翻倍。
更麻烦的是,这5000个无效查询和5000个正常查询混杂在一起,数据库的查询优化器、连接管理、事务处理都被迫为这些无效工作分配资源,从监控上看,数据库CPU、IO、内存的使用率都在上升,但业务成交量没有任何变化,这就是“隐性压力”的含义。
常见疑问解答
缓存穿透和缓存雪崩是一回事吗
不是,缓存雪崩是指大量缓存key在同一时间段内集中过期,导致所有请求都落到数据库上,数据库压力瞬间达到峰值,而缓存穿透是查询不存在的数据,每一次查询都无法被缓存命中,两者的处理方式也不同,雪崩需要做过期时间打散、热点数据永不过期,穿透需要做参数校验、空值缓存和布隆过滤器。
大促时布隆过滤器本身会出现瓶颈吗
布隆过滤器本身是一个位数组,判断一个元素是否存在只需常数时间(O(1)),性能远高于访问Redis或数据库,但要注意的是,布隆过滤器的构建需要全量数据,大促期间如果有大量新增商品而没有同步更新布隆过滤器,会导致这些新商品被误判为“不存在”,产生漏请求,所以大促前需要确保商品数据的同步链路是通的。
空值缓存会不会把Redis空间打满
有可能,这也是为什么空值缓存需要设置较短的过期时间、限制空值key的内存占比的原因,大促期间可以额外增加一个空值key的监控项,如果占比超过阈值就触发告警,并动态调整过期时间,更稳妥的做法是给空值key加一个随机过期时间,避免大量空值key同时过期导致缓存雪崩。
缓存穿透在大促期间对数据库造成的压力是真实且隐蔽的,它不直接导致系统崩溃,但会侵蚀数据库的每一分余量,让本已紧张的系统雪上加霜,大促前的技术准备,应该把缓存穿透治理作为一项必做项,而不是可选项从入口参数校验到空值缓存再到布隆过滤器,每一层都在帮数据库挡住那些“不该来的请求”。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/635605.html


