缓存穿透的典型场景:有人拿脚本扫你的站,每次请求一个根本不存在的ID,比如 /goods.php?id=99999999。Redis里查不到,请求全落到MySQL,而MySQL也查不到,什么都不缓存。下一波请求还是这个流程,数据库白挨打,缓存形同虚设。
跟缓存击穿(某个热key过期)不一样,穿透的点是“查询结果为空”这种结果默认不进缓存。解决方案也就两个思路:要么把“空”也缓存起来,要么在缓存前面挡一道,让不存在的key根本到不了数据库。
// 方案一:空值缓存,短过期时间
$key = 'goods:' . $id;
$data = $redis->get($key);
if ($data === 'NULL_MARK') {
exit('商品不存在'); // 命中空值标记,直接返回
}
if ($data === false) {
$row = $db->query("SELECT * FROM goods WHERE id=?", [$id]);
if (!$row) {
$redis->setex($key, 60, 'NULL_MARK'); // 空 };
空值缓存的过期时间别设长,30到60秒够了。设太长有个副作用:后台刚补录了这个商品,前台一分钟内还是“不存在”,运营会以为出bug了。真遇到这种情况,录入成功后顺手删一次key就行。
第二层保险是布隆过滤器。原理不用抠细节,只要知道:它能判断“这个key一定不存在”或“可能存在”,误判率可以调。把全量商品ID预热进布隆过滤器,请求先问它一声,一定不存在的直接拒绝,连Redis都不用查。
# Redis 4.0+ 自带布隆过滤器模块(RedisBloom)
# 插入全量商品ID
BF.ADD goods_filter 10001
BF.ADD goods_filter 10002
# 查询:0=一定不存在 1=可能存在
BF.EXISTS goods_filter 99999999
验证效果很直观:压测工具拿不存在的ID打接口,开穿透防护前数据库QPS飙到几千,开了之后Redis命中空值标记,数据库QPS归零。看一眼redis-cli的info stats里keyspace_misses是否还在疯涨,就知道防护有没有生效。
个人建议小站先用空值缓存就够了,布隆过滤器那套适合数据量大、攻击面广的站。另外别忘了一个更省事的兜底:对参数做合法性校验,ID不是数字、超出合理范围的直接404,很多“穿透攻击”其实就是拿随机字符串扫的,一层is_numeric就能挡掉大半。
数据来源:Redis官方文档
A5创业网 版权所有