缓存穿透时GET返回null需区分真假缺失,应使用布隆过滤器拦截非法ID或写入空值+短过期(如60秒);无需先EXISTS,因GET本身已含存在性判断;条件逻辑应在应用层处理,而非塞入Redis。

缓存穿透时 GET 返回 null 怎么办
Redis 本身不支持“条件读取”,GET 就是查键存在与否,查不到就返回 null。业务里看到 null,不能直接认定“数据不存在”,得区分是“真没数据”还是“缓存穿透”。否则一窝蜂打到 DB,DB 就崩了。
常见错误现象:用户查一个根本不存在的订单号 order:999999999,Redis 没这个 key,后端直接查库、查不到、不写缓存,下次还来,反复触发。
- 用布隆过滤器(Bloom Filter)在接入层拦截非法 ID —— 要求 ID 有固定格式或可预估范围
- 对确认不存在的数据,写入空值 + 短过期(比如
SET order:999999999 "" EX 60),防止重复穿透 - 注意:空值缓存不能设太长,否则真实数据上线后会被“捂住”;也不能设太短,否则起不到保护作用
GET 前要不要先 EXISTS
没必要。Redis 的 GET 本身就带存在性判断,返回 null 和 key 不存在是同一语义。多一次 EXISTS 只会增加一次网络往返和命令开销,纯属冗余。
使用场景:高并发读热点 key 时,每多一次 round-trip 都可能放大延迟抖动。
-
EXISTS key和GET key是两个独立命令,无法原子合并 - 某些客户端(如 Jedis)的
get()方法底层就是发GET,拿到null就返回null,无需额外判断 - 如果真想合并逻辑,可用 Lua 脚本封装,但绝大多数情况得不偿失
缓存命中率低,是不是该加条件判断逻辑
不是。缓存不该承担“条件路由”的职责。决定“走不走缓存”的逻辑,必须放在业务代码里,而不是塞进 Redis。
比如:用户 A 查自己订单列表,缓存 key 是 orders:user:123;用户 B 查自己的,是 orders:user:456。这时候你不能指望 Redis 根据 user_id 自动选 key——它压根不知道 user_id 是啥。
- 条件判断(如权限校验、状态过滤、时间窗口)必须在应用层做,再决定是否查缓存、查哪个 key、是否跳过缓存直连 DB
- 把业务规则硬塞进缓存层,会导致 key 设计混乱、失效难管理、监控失真
- 性能影响:Lua 脚本里做复杂条件判断,会阻塞 Redis 单线程,尤其在高 QPS 下容易拖慢整个实例
用 GET + 应用层 if-else 判断够不够用
够用,而且是最常用、最可控的方式。Redis 只负责“存什么、取什么”,剩下的事交给代码更清晰、更易测、更易调。
示例场景:只对 status=1 的订单走缓存,其他走 DB。
String cacheKey = "order:" + orderId;
String cached = redis.get(cacheKey);
if (cached != null) {
Order order = parse(cached);
if (order.getStatus() == 1) {
return order; // 缓存命中且符合条件
}
}
// 否则查 DB,再按规则决定是否回填缓存
- 注意:不要在缓存里存原始 JSON 或未结构化数据,否则每次都要反序列化才能判断字段——浪费 CPU,也增加出错概率
- 如果判断逻辑复杂(比如多字段组合条件),建议缓存中存最小必要字段(如
{"status":1,"updated_at":1712345678}),避免全量解析 - 别忘了给缓存设置合理过期时间,否则旧状态会长期掩盖新状态
真正容易被忽略的是缓存与业务状态的一致性边界:什么时候该删缓存、什么时候该更新、有没有并发写导致的中间态。这些没法靠“条件走缓存”绕过去,得靠明确的失效策略和幂等设计兜底。

















