redis原生无穿透计数器,须业务层实现;incr易误判合法请求且存在竞态,zset计数可能成瓶颈,应以布隆过滤器和空值缓存为主防,计数仅用于监控告警与策略优化。

缓存穿透本身不会自动触发计数器机制,Redis 原生不提供“穿透计数器”功能;所有计数逻辑必须由业务代码显式实现,且需警惕并发写入导致的计数偏差。
为什么不能直接用 Redis INCR 做穿透计数
INCR 看似简单,但用于穿透防护时容易误判:它统计的是「所有未命中缓存的请求」,包括合法但首次访问的热 key、正常冷启动流量、以及真正的恶意穿透请求。一旦把 INCR 结果当作拦截依据,会误杀大量有效请求。
更关键的是,INCR 和后续的 DB 查询之间存在竞态窗口——多个线程同时 INCR 后都看到值
常见错误现象:INCR hit_count:goods:123 返回 3,但三秒内仍有 8 个线程查了数据库,计数器完全没拦住。
建议做法:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
- 只对明确标记为「可疑」的请求做计数,比如带特定 UA、来源 IP 段、或经过前置布隆过滤器判定为“极大概率不存在”的 key
- 计数 + 过期必须原子执行,优先用
EVAL脚本封装INCR和EXPIRE - 阈值不宜设死,例如 100 次/分钟,而应结合时间窗口滑动统计(如用
ZSET存时间戳)
用 ZSET 实现滑动窗口计数(推荐)
比单纯 INCR 更可靠:记录每次穿透请求的时间戳,查询时只统计最近 N 秒内的数量。
示例逻辑(伪代码):
zadd penetration_log:goods:123 <unix_timestamp><request_id> zrembylex penetration_log:goods:123 - (current_timestamp - 60) zcard penetration_log:goods:123</request_id></unix_timestamp>
注意点:
-
zrembylex要求 score 是整数时间戳,且 key 必须用ZSET,不能用LIST(无法按范围删除) - 每个 key 单独一个
ZSET,避免不同商品互相干扰;key 名建议带业务前缀,如penetration_log:order:id_ - 如果 QPS 极高(>5k/s),ZSET 的
ZCARD+ZREMBYLEX组合可能成瓶颈,此时应降级为「布隆过滤器 + 空值缓存」主防,计数仅作监控告警
计数器只是辅助手段,不是核心防线
真正扛住穿透压力的,永远是布隆过滤器和空值缓存这两层。计数器的作用仅限于:
- 识别异常 IP 或 client_id,触发临时封禁(配合
SET key value EX 300 NX) - 在后台聚合后生成报表,用于优化布隆过滤器的误判率参数
- 当空值缓存被频繁驱逐时,用计数判断是否该扩容 Redis 内存或调整 LRU 策略
最容易被忽略的一点:计数器本身也会被穿透——攻击者可故意刷大量随机 key,让 ZSET 膨胀,最终拖慢整个 Redis 实例。所以务必给每个计数 key 设置 EX 过期,且过期时间要短于滑动窗口长度(比如窗口 60 秒,EX 90)。










