Redis穿透本质是无效Key高频查询,即攻击者用随机ID等非法key绕过校验直接冲击数据库,解决方案包括参数校验、空值缓存和布隆过滤器拦截。

Redis穿透的本质是“无效Key高频查询”
不是所有请求都该进Redis计数。比如攻击者用随机userId或伪造api_key反复请求,每次都会触发GET查Redis,而这些Key根本不存在——既没命中缓存,也没对应业务数据,纯属空转消耗。这种请求不走限流逻辑(因为没匹配到规则),却直接打穿Redis连接和CPU,比正常限流更伤。
关键判断点:限流前必须先做有效请求识别。不能等RRateLimiter.tryAcquire()或INCR执行完才发现是垃圾请求。
- 在网关过滤器中,优先校验
Authorization头是否格式合法(如Bearer xxx)、长度是否合理(JWT通常200~400字符),非法格式直接400拦截,不碰Redis - 对已知固定路径的接口(如
/api/v1/order/{id}),用正则预检{id}是否为合法UUID或数字,非格式化ID直接拒绝 - 配置Nginx层做基础IP频次拦截(
limit_req),把明显扫描行为(如1秒内10+不同路径)在入口就干掉,避免触达Spring Cloud Gateway
黑名单写入必须带原子性与过期时间
很多人用redisTemplate.opsForValue().set("blacklist:192.168.1.100", "1")加黑名单,但漏了两个致命问题:没设过期、没保证写入原子性。结果就是误封永久生效,或者并发写入时覆盖TTL导致失效。
正确做法是统一用SET key value EX seconds NX命令,由Redis保证“仅当key不存在时才设置并指定过期”。Java里对应:
redisTemplate.execute((RedisCallback<Boolean>) connection -> {
byte[] key = "blacklist:".concat(ip).getBytes();
byte[] value = "1".getBytes();
return connection.set(key, value, Expiration.from(30, TimeUnit.MINUTES),
RedisStringCommands.SetOption.SET_IF_ABSENT);
});
- 过期时间必须显式设定,推荐30分钟起步,避免长期误封影响真实用户
- 不要用
HASH存黑名单(如HSET blacklist_ips 192.168.1.100 1),因为HDEL无法自动过期整个结构,得额外起定时任务清理 - 如果用Redisson,直接调
RBucket.set(value, Duration.ofMinutes(30)),它底层已封装原子写入
限流与黑名单必须分层触发,不能混为一谈
限流是“控速”,黑名单是“断连”,两者目标不同、阈值不同、生命周期不同。常见错误是把tryAcquire() == false直接等同于“该IP进黑名单”,结果导致正常用户重试几次就被永久拉黑。
真实生产环境需要三级响应:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 第一级:单IP每秒超
rageLimit=5次 → 返回429,不记录黑名单 - 第二级:同一IP在
time-interval=60秒内累计被限流protectLimit=10次 → 写入黑名单,封禁limit-time=300秒 - 第三级:黑名单中IP再次发起任意请求 → 网关过滤器直接
response.setStatusCode(HttpStatus.FORBIDDEN),不执行后续任何逻辑(包括JWT解析)
注意:protectLimit和limit-time必须可动态配置。硬编码在代码里等于放弃运维能力——某次活动突然被刷,你得改代码、打包、上线,而别人改个Redis里的Hash字段就生效了。
网关层限流必须绕过Spring Security的Filter链
如果你把限流逻辑写在@ControllerAdvice或普通AOP切面里,请求已经走完Spring Security的UsernamePasswordAuthenticationFilter甚至BasicAuthenticationFilter,意味着:JWT解析、密钥验签、DB查用户信息全干完了,才轮到限流——此时Redis穿透早发生了,CPU也烧了。
正确位置只有两个:
- Spring Cloud Gateway的
GlobalFilter实现类,且getOrder()返回值必须小于SecurityWebFilterChain的顺序(默认-100),建议设为-200 - 或Nginx的
lua-resty-limit-traffic模块,在TCP层就完成令牌桶计算,完全不交到JVM
验证是否生效:用curl -H "X-Real-IP: 1.2.3.4" http://gateway/api/test压测,同时redis-cli monitor | grep "rl:1.2.3.4"看是否有键写入。没有,说明限流逻辑根本没触发。
最易被忽略的一点:Redis连接池配置。默认LettuceClientConfigurationBuilder的maxInMemorySize是0,即不限制,高并发下大量连接未释放会拖垮网关。必须显式设clientResources().ioThreadPoolSize(4)和poolConfig().maxIdle(16),否则再好的算法也卡在IO线程上。

















