购物车场景下“读穿透”本质是缓存穿透,必须结合用户id校验、布隆过滤器预筛和空对象兜底三者协同防御:仅用空值缓存会导致无效key堆积、内存暴涨、掩盖真实异常;布隆过滤器须基于全量user表构建并增量同步;接口层须强制jwt校验与userid一致性比对。

直接结论:购物车场景下“读穿透”本质是缓存穿透,但不能只靠缓存空值解决——必须结合用户ID校验 + 布隆过滤器预筛 + 空对象兜底,三者缺一不可。
为什么购物车不能只用set(key, "", 60, SECONDS)防穿透
购物车 key 通常是 "cart:" + userId 这类结构,userId 合法性极难穷举。如果攻击者用随机 userId=9999999999 批量请求,缓存空值会迅速堆积大量无效 key,Redis 内存暴涨且 TTL 管理开销剧增。更严重的是,空值缓存会掩盖真实异常(比如用户被禁用、账号注销),导致后续逻辑误判。
常见错误现象:redis-cli --scan --pattern "cart:*" | wc -l 发现数百万 cart key,其中 70% 是空值;监控显示 Redis 内存使用率每小时涨 5%,但实际活跃用户数稳定。
实操建议:
- 空对象仅用于已确认存在的用户(如登录态有效、DB 中查得 user 记录)但 cart 为空的场景
- 对未登录或 userId 格式非法(如负数、超长数字、含字母)的请求,直接在网关层拦截,不进缓存层
- 空对象过期时间严格控制在
30 秒以内,避免长期占位
布隆过滤器必须覆盖“合法用户ID全集”,而非“购物车存在用户”
很多团队把布隆过滤器建在 cart 表上,只塞入当前有购物车数据的 userId —— 这完全无效。攻击者只要找一个“有账号但没加过购物车”的用户 ID,就能绕过过滤器直击数据库。
正确做法是将布隆过滤器构建在 user 表主键上,且必须包含所有历史注册过的 userId(含已注销但未物理删除的)。布隆过滤器本身不存数据,只回答“这个 userId 大概率 存在”。它不是为了 100% 拦截,而是把 99.9% 的恶意请求挡在 Redis 之前。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
实操建议:
- 使用 Redisson 的
RSetCache或自建RBloomFilter,初始化时从 MySQL 分批加载 user.id(建议每批 10 万,避免 OOM) - 误判率设为
0.01(即 1%),兼顾内存与精度;每日凌晨通过 Canal 监听 user 表新增,增量更新布隆过滤器 - 应用层调用顺序必须是:
checkBloom(userId) → false → 直接返回404;true → 查 Redis cart → null → 查 DB user → 不存在则拒掉
购物车读接口必须做两级参数校验
单纯依赖缓存或布隆过滤器,会在边界场景失效。例如:用户 A 刚注销,布隆过滤器尚未同步;或 userId 被篡改但格式合法(如 userId=123456 实际是 B 用户的 ID)。
所以购物车读接口(如 GET /carts)必须强制携带登录态凭证(如 JWT),并在 Controller 层做硬比对:
实操建议:
- 从 JWT 解析出
sub字段,与 URL/Query 中的userId严格相等校验,不一致直接 403 - 若接口支持“查看他人购物车”(如分享场景),必须走独立权限校验逻辑,绝不能复用同一套缓存 key 结构
- 所有购物车相关 Redis 命令(
HGETALL、HLEN)前,先执行EXISTS cart:{userId},避免对空 key 频繁执行大命令
真正容易被忽略的点是:布隆过滤器的重建时机和一致性。如果 Canal 监听延迟超过 5 分钟,新注册用户在过滤器生效前就会被判定为“不存在”,导致正常请求被误杀。必须把布隆过滤器更新纳入发布 checklist,并在监控中单独看它的 miss rate 曲线——持续高于 5% 就说明数据源同步出了问题。










