Spring Cloud Gateway 分布式 IP 限流方案

📅 2026/7/31 18:45:48 👁️ 阅读次数 📝 编程学习
Spring Cloud Gateway 分布式 IP 限流方案

一、背景与目标

在微服务架构中,网关作为流量入口,需要具备基础的防护能力。本文介绍一种基于 Redis + Lua 脚本的分布式 IP 限流方案,在网关层实现每个 IP 每秒最多 N 次请求的限制,防止恶意刷接口、暴力破解等行为。

核心目标

目标说明
分布式限流网关多节点共享计数,精确全局限流
高性能单次 Redis 调用完成所有操作
原子性Lua 脚本保证无竞态条件
降级保护Redis 异常时自动放行,不影响业务
可观测日志记录真实计数,反映攻击猛烈程度

二、整体架构

┌─────────────┐ │ 客户端 │ │ IP: 1.2.3.4│ └──────┬──────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Spring Cloud Gateway │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ IpRateLimitGlobalFilter │ │ │ │ ① 提取真实 IP(X-Forwarded-For 优先) │ │ │ │ ② 构造 Key: rate_limit:ip:{IP}:{秒时间戳} │ │ │ │ ③ 执行 Redis Lua 脚本(原子 INCR + EXPIRE) │ │ │ │ ④ 判断: count > limit ? 429 : 放行 │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Redis │ │ │ │ Key: rate_limit:ip:1.2.3.4:1734567890 │ │ Value: 3 (当前秒内该 IP 已请求 3 次) │ │ TTL: 1秒 │ └─────────────────────────────────────────────────────────────┘

三、核心实现

3.1 限流配置(application.yml)

rate-limit:ip:enabled:truelimit:30# 每秒最多允许 30 次请求

3.2 核心代码

配置类
@Component@ConfigurationProperties(prefix="rate-limit.ip")publicclassIpRateLimitProperties{privatebooleanenabled=true;privateintlimit=30;// getter / setter}
Lua 脚本(核心)
localkey=KEYS[1]localttl=tonumber(ARGV[2])-- 原子性自增localcurrent=redis.call('INCR',key)-- 首次访问设置过期时间ifcurrent==1thenredis.call('EXPIRE',key,ttl)end-- 返回真实计数returncurrent
限流过滤器
@ComponentpublicclassIpRateLimitGlobalFilterimplementsGlobalFilter,Ordered{privatestaticfinalintWINDOW_SECONDS=1;privatestaticfinalStringLUA_SCRIPT="local key = KEYS[1]\n"+"local ttl = tonumber(ARGV[2])\n"+"local current = redis.call('INCR', key)\n"+"if current == 1 then\n"+" redis.call('EXPIRE', key, ttl)\n"+"end\n"+"return current";privatefinalDefaultRedisScript<Long>limitScript=newDefaultRedisScript<>(LUA_SCRIPT,Long.class);@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){if(!rateLimitProperties.isEnabled()){returnchain.filter(exchange);}Stringip=getRealIp(exchange.getRequest());intlimit=rateLimitProperties.getLimit();Stringkey="rate_limit:ip:"+ip+":"+Instant.now().getEpochSecond();returnMono.from(redisTemplate.execute(limitScript,Collections.singletonList(key),String.valueOf(limit),String.valueOf(WINDOW_SECONDS))).flatMap(currentCount->{if(currentCount!=null&&currentCount>limit){log.warn("IP限流触发 - IP: {}, 计数: {}, 阈值: {}/s",ip,currentCount,limit);returnbuildTooManyRequestsResponse(exchange);}returnchain.filter(exchange);}).onErrorResume(e->{log.error("Redis异常,降级放行 - IP: {}",ip,e);returnchain.filter(exchange);});}@OverridepublicintgetOrder(){returnOrdered.HIGHEST_PRECEDENCE;// 最高优先级}}

四、为什么用 Lua 脚本?

方式原子性Redis 调用次数并发安全
Java 两步操作(INCR + EXPIRE)2 次有竞态
Redis 事务(MULTI + EXEC)⚠️2 次只保证顺序,不保证原子
Lua 脚本1 次✅ 单线程原子执行

关键点:Redis 执行 Lua 脚本时,整个脚本是一个原子操作,不会被其他命令打断。


五、关键设计决策

设计点选择原因
限流维度按 IP最常见的防刷维度,区分不同客户端
窗口算法固定窗口(1秒)实现最简单,性能最高,防刷够用
存储引擎Redis网关多节点共享计数,保证一致性
Key 设计rate_limit:ip:{IP}:{秒时间戳}自然分片,自动过期,无需手动清理
返回值真实计数日志可观测攻击严重程度,方便调优
降级策略Redis 异常时放行限流是辅助功能,不影响核心业务

六、压测验证

测试配置

rate-limit:ip:enabled:truelimit:30

JMeter 测试结果

线程数Ramp-Up预期放行实际放行结论
500s3030✅ 精准
300s3030✅ 精准
101s~8(跨窗口)8✅ 合理

运行日志

DEBUG IP限流检查 - IP: 127.0.0.1, Key: rate_limit:ip:127.0.0.1:1734567890, Limit: 30/s WARN IP限流触发 - IP: 127.0.0.1, 当前计数: 31, 阈值: 30/s WARN IP限流触发 - IP: 127.0.0.1, 当前计数: 45, 阈值: 30/s

七、注意事项

7.1 Nginx 需透传真实 IP

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr;

7.2 Redis 连接池配置

spring:redis:host:${REDIS_HOST}port:6379timeout:2000mslettuce:pool:max-active:20max-idle:10min-idle:5

7.3 限流阈值调优

场景建议阈值说明
普通 API30-50/s覆盖正常用户浏览行为
登录/注册接口3-5/s防暴力破解
批量查询接口50-100/s列表加载等场景

八、方案优缺点

✅ 优点

  • 极简高效:一个 Filter + 一个 Lua 脚本搞定
  • 分布式支持:Redis 共享计数,网关集群一致
  • 原子性保障:Lua 脚本保证无竞态
  • 降级保护:Redis 异常不影响业务
  • 可观测性强:日志记录真实计数

⚠️ 局限

  • 固定窗口边界问题:1 秒末 + 2 秒初允许 2 倍流量(防刷场景可接受)
  • 依赖 Redis:需要 Redis 高可用保障
  • 单维度限流:仅按 IP 限流,如需多维度需扩展

九、总结

本文介绍了一种生产级分布式 IP 限流方案,核心思想是:

在网关层用 Redis + Lua 做原子计数,每个 IP 每秒独立计数,超过阈值返回 429。