连载中 3/20

穿透:查一个不存在的商品

2026-05-25 · 6136 阅读 · 0 评论 · 0 赞

恶意的 id=-1

缓存的价值建立在「同一个 key 会被反复读」上。可如果有人拿一批根本不存在的商品 id——负数、随机数、超长字符串——狂刷接口呢?每个 key 在缓存里都查不到,每个请求都穿过缓存打到 DB,缓存形同虚设,数据库替它挨了全部子弹。这就是缓存穿透:查询的数据在缓存和数据库里都不存在。

ItemDetail d = redis.get("item:detail:-1");   // null,未命中
d = itemDao.loadDetail(-1);                   // null,DB 也白查一次
// 攻击者换着 id 刷:每个请求都完整穿过缓存层,DB 扛下全部流量

注意它与「正常的未命中」不同:正常业务里新商品上架前的偶尔未命中无伤大雅,穿透是大量请求系统性地产出空结果——要么恶意,要么 bug。

解法一:空值缓存

最直接的思路:既然「不存在」这个事实也会被反复查询,那就把它也缓存起来——查库确认不存在后,写入一个短 TTL 的空值标记,后续同样的 id 在缓存层就直接返回。

ItemDetail d = itemDao.loadDetail(id);
if (d == null) {
    redis.setex(key, 60, "NULL");     // 「不存在」也缓存 60 秒
    return null;
}
redis.setex(key, 1800, toJson(d));

简单有效,但有两个注意点:TTL 要短(商品真上架后,最多脏一分钟,可接受);攻击者若每次都换随机 id,空值缓存会反过来把缓存打满——所以空值缓存要配容量上限(比如只缓存空值 key 的哈希前缀),或者交给更彻底的方案。

解法二:布隆过滤器

在缓存前面再加一道闸:把全部合法商品 id 预先装进一个布隆过滤器——一个位数组加 k 个哈希函数的巧结构。它的承诺很有性格:说「不存在」就一定不存在,说「存在」可能误判。用误判的那一点点概率,换来了惊人的空间效率——上亿级 id 也就百 MB 内存。

// 启动时把全部合法商品 id 装进布隆过滤器
BloomFilter<Long> filter = BloomFilter.create(
        Funnels.longFunnel(), 10_000_000, 0.01);   // 预期 1000 万,误判率 1%

public ItemDetail getItem(Long id) {
    if (!filter.mightContain(id)) {
        return null;               // 一定不存在:直接挡掉,不碰缓存不碰 DB
    }
    // ... 走正常缓存流程(误判的极少数会落到这,无害)
}

两个工程细节:标准布隆过滤器不支持删除(下架商品只能等全量重建,或换 Counting Bloom / RedisBloom 模块);数据量大时重建耗时,要用定时任务在低峰期增量维护。

解法三与怎么选

第三道闸在更外面:入口校验。id 格式不对(雪花 id 有固定长度范围)、参数越界、同一来源请求频率异常——在网关和参数校验层就拦掉,成本最低,也最治本。它治的是已知规则,布隆过滤器治的是海量未知 id,两者不冲突。

解法优点代价
空值缓存实现简单,立即生效随机 id 会占内存,需配容量上限
布隆过滤器内存极省,挡得彻底有误判率,删除困难,需维护重建
入口校验与限流治本,成本最低只能挡已知规则的攻击

实战组合拳:入口校验打头阵,布隆过滤器守中军,空值缓存做补丁,限流系列讲过的网关限流再兜一层底。穿透挡住了「查不到的」,可「查得到的」也有危险时刻——爆款商品缓存过期的那一秒,就是下一篇的主角:击穿。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#缓存穿透#布隆过滤器#空值缓存#入口校验#Redis

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞