缓存穿透是恶意攻击者批量构造不存在的ID(如user_id=-1、sku_id=9999999999)或爬虫遍历非连续主键,绕过缓存直击数据库,导致数据库压力剧增甚至宕机。

缓存穿透的高频攻击手段:不是误操作,是刻意绕过
缓存穿透不是偶发的参数错误,而是有明确攻击意图的行为。最典型的高频手段就是用脚本批量构造根本不存在的 id 或 key,比如 user_id=-1、sku_id=9999999999、order_no=ABC123XYZ 这类明显超出业务范围的值。黑产工具会自动探测接口路径和参数名,然后用随机数、负数、超长字符串、特殊字符组合发起请求,目标就是让每次查询都落空,持续消耗数据库连接和 CPU。
另一个容易被忽视的是爬虫式遍历:比如主键是自增整型,攻击者从 1 开始逐个请求 /api/user/1、/api/user/2……直到 /api/user/100000,中间大量 ID 实际并不存在。这类请求单次压力不大,但持续不断,且难以靠频率限流识别——因为每个请求的 key 都不同。
缓存空值(NULL 缓存)为什么必须设短过期时间?
空值缓存是防御穿透最直接、最低成本的手段,但很多人只写 set("user:999", "NULL") 却忘了设过期时间,结果导致缓存里积压大量无效 key,占用内存,还可能掩盖真实数据上线后的可见性问题。
- 空值必须设过期,推荐
300秒(5 分钟)以内,太长会导致“假空”长期存在;太短(如 30 秒)则起不到缓冲作用 - 不要用
null字面量直接序列化进 Redis,Java 中JSON.toJSONString(null)是字符串"null",易与业务字段混淆;建议统一用明确标记如"EMPTY"或空 JSON 对象"{}" - 读缓存时要先判断是否为该空值标记,而不是只判
== null—— 因为 Redis 返回可能是字符串,不是 Java 的null
布隆过滤器不是万能的,它在哪几个环节容易翻车?
布隆过滤器在穿透防御中常被神化,但它本质是个概率结构,实际落地时有三个关键断点:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 初始化不全:只加载了当前库的主键,但分库分表后新表没同步加载,或冷热分离后历史数据未纳入,导致“漏判”——本该拦截的请求放行了
-
动态数据不更新:用户注册、商品上架等写操作发生后,没同步
bloomFilter.put(key),新 key 在过滤器里查不到,首次请求仍穿透 -
误判率设置失当:设成
0.001看似很准,但内存占用翻倍;设成0.1虽省内存,但每 10 次请求就有 1 次被误判为“不存在”,反而增加无效 DB 查询
所以生产环境别单独依赖布隆过滤器,它必须和空值缓存配合:过滤器说“可能存在”,才查 Redis;说“肯定不存在”,就直接返回;而 Redis 里还要兜底存空值,防过滤器漏判。
接口层校验为什么不能只靠正则?
很多团队加了一条 if (userId 就以为万事大吉,但攻击者早就不玩负数了。真正危险的是看似合法实则无效的值:
- 手机号格式正确(11 位、以 1 开头),但号码从未注册过
- ID 是 long 类型最大值
9223372036854775807,程序能解析不报错,但 DB 里不可能有 - 订单号含时间戳 + 随机串,攻击者按规则生成大量组合,命中率低但总量大
所以校验要分层:前端做基础格式限制(减少无效流量),网关层做白名单/长度/范围硬约束(如 userId 必须在 [1, 10000000]),业务层再结合布隆过滤器或缓存空值兜底。单一层级拦不住穿透。

















