缓存 null 值本身不能防穿透,正确做法是存空字符串等可识别标记值并分级设置短 ttl,且必须配合布隆过滤器与结构化参数校验。

缓存 null 值本身不能防穿透,反而可能放大风险——关键在于怎么存、存什么、存多久。
为什么 redisTemplate.opsForValue().set(key, null, 60, TimeUnit.SECONDS) 是错的
多数 Redis 客户端(如 Spring Data Redis)序列化 null 时会写入 nil,读出来是空响应,业务层无法区分“真没数据”和“缓存失效/未写入”。这导致:
- 每次请求都 fallback 到 DB,穿透照旧
- 监控里看不到空值缓存命中,误判防御生效
- 日志中大量 redisTemplate.opsForValue().get("user:-1") 返回 null,但你不知道它压根没进缓存
正确做法是显式存一个可识别的标记值:redisTemplate.opsForValue().set(key, "", 60, TimeUnit.SECONDS) 或 "NULL" 字符串,且读取时必须做字符串判等,而非判空。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
空值缓存的 TTL 必须短,且不能一概而论设 5 分钟
设固定 300 秒对多数场景太激进:
- 用户 ID 为 Long 类型,但请求传了 "abc" —— 这类非法格式应由参数校验拦截,不该走到缓存层
- 真实稀疏 ID(如注册用户跳号严重),user:999999999 虽然不存在,但可能是合法范围内的 ID,缓存空值 5 分钟会阻塞后续真实查询
- 攻击者用 user:-1、user:0 扫描,这类语义非法 key 应该秒级过期(如 10 秒),而非给它 5 分钟户口
建议按 key 类型分级:
- 格式非法 key(含非数字、负数、超长)→ 10 秒
- 格式合法但 DB 查无结果 → 60 秒
- 监控发现某 key 空值命中率持续 >80%,自动降为 5 秒并告警
空值缓存必须配合布隆过滤器,否则就是裸奔
只靠空值缓存,等于把防御阵地从网关后撤到 Redis 层,攻击流量已穿过负载均衡、API 网关、Controller 校验,才在 Redis 里被拦下。此时:
- 网络带宽、连接池、线程调度已消耗
- Redis 自身 QPS 承压(尤其高并发查 user:-1)
- KEYS user:* 类扫描命令可能触发慢日志
真正有效的链路是:
Gateway → 布隆过滤器(预热全量合法 ID)→ 参数校验(@Min(1) + @Valid)→ 缓存空值(兜底)→ DB
布隆必须是全局单例、初始化加载、随写库更新(如新增用户后调用 bloomFilter.add(userId)),不能每次请求 new 一个。
最容易被忽略的点:空值缓存和布隆都防不住“格式合法但语义越界”的请求
比如:
- user_id=123 是合法 Long,但业务规定只服务 2024 年后注册用户,123 是 2018 年老 ID → 布隆放行,空值缓存也生效,但这是业务逻辑空,不是穿透
- phone="+8613800138000" 符合正则,但号码库从未录入 → 同样绕过布隆与空值策略
这类请求必须靠结构化校验兜底:^1[3-9]\d{9}$ 验证手机号、@Range(min = 1, max = 999999999) 限制 ID 范围、查表确认租户归属。别信 Swagger 注解单独生效——必须配 @Valid + 全局 @RestControllerAdvice 拦截。










