连载中 3/20

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

2026-05-25 · 6129 阅读 · 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)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞