一、为什么要做分布式限流?
很多人做后端开发时都遇到过这种情况:大促、热点事件(比如明星带货、突发新闻)突然来一波流量,平时跑得好好的系统瞬间卡死,要么返回超时,要么直接报502。更糟的是,还可能因为流量太大把数据库、缓存都打崩,导致整个服务集群都挂掉,得花好几个小时恢复。
限流就是解决这个问题的核心手段,简单说就是给系统设个“流量闸门”,比如每秒最多允许100个请求进来,超过的直接拒绝,保证系统不会被冲垮。但如果是分布式架构(现在几乎所有中大型项目都是),每个服务实例自己管自己的限流是没用的——比如每个实例限100,10个实例就是1000,但如果某一个实例瞬间扛了1000个请求,还是会崩。所以必须做“分布式限流”,让所有实例共用一个限流规则,这也是我们用Spring Cloud Gateway加Redis的原因。
二、核心技术栈说明
整个方案只用到一套技术栈,所有示例都基于这个环境:
- Spring Cloud Gateway:版本3.1.5(和Spring Boot 2.6.13兼容,是比较稳定的版本)
- Redis:版本6.2.6(用单机版做测试,集群版逻辑完全一样)
- Java:JDK 11(Spring Cloud 3.x推荐的版本)
三、具体配置步骤
3.1 准备工作:引入依赖
首先得在Gateway项目的pom.xml里加两个核心依赖:一个是Gateway的限流组件,一个是Redis的客户端。
<!-- Gateway的限流核心依赖,自带了基于Redis的限流逻辑 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.5</version>
</dependency>
<!-- Redis的客户端,用来和Redis通信 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.6.13</version>
</dependency>
3.2 配置Redis连接
接下来要告诉Gateway怎么连Redis,在application.yml里加Redis的配置:
spring:
redis:
host: 127.0.0.1 # Redis的地址,集群版就写多个节点的地址
port: 6379 # Redis的默认端口
password: # 如果Redis没设密码就留空,有密码就填
database: 0 # 用Redis的第0个库,一般默认就行
3.3 编写限流配置类
Gateway的限流需要我们自己写一个配置类,用来定义限流的规则(比如按什么维度限流、每秒限多少)。这里要用到Redis的令牌桶算法,Gateway已经帮我们封装好了,我们只需要调用就行。
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimitConfig {
// 定义限流的维度:这里用请求的IP来限流,每个IP每秒最多5个请求
@Bean
public KeyResolver ipKeyResolver() {
// 返回一个Mono,Mono是Spring WebFlux里的响应式编程对象,用来处理异步结果
return exchange -> Mono.just(
// 从请求的上下文中拿到客户端的IP地址
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
// 定义Redis限流的参数:每秒生成的令牌数、令牌桶的最大容量
@Bean
public RedisRateLimiter redisRateLimiter() {
// 第一个参数是replenishRate:每秒生成的令牌数,也就是每秒允许的最大请求数
// 第二个参数是burstCapacity:令牌桶的最大容量,用来应对突发的少量流量(比如突然来2个请求,只要桶里有就可以过)
return new RedisRateLimiter(5, 10);
}
}
这里解释一下令牌桶算法的逻辑:系统每秒会往桶里放5个令牌,每个请求进来都要拿一个令牌才能通过;如果桶里没令牌了,就拒绝这个请求。令牌桶最多能存10个令牌,比如某一秒系统没流量,桶里会攒5个,下一秒再放5个,最多到10个就不再加了,这样可以应对短时间的突发流量。
3.4 给路由绑定限流规则
最后一步是把限流规则绑定到具体的路由上,还是在application.yml里配置:
spring:
cloud:
gateway:
routes:
# 定义一个路由,用来转发到我们的业务服务(比如用户服务)
- id: user-service-route # 路由的唯一ID,随便起
uri: lb://user-service # 转发的目标地址,lb是负载均衡,后面是服务名
predicates:
- Path=/user/** # 所有以/user开头的请求都走这个路由
filters:
# 绑定限流过滤器,参数是限流的维度(ipKeyResolver)和限流参数(redisRateLimiter)
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5 # 这里可以覆盖配置类里的参数,每个路由可以单独设规则
redis-rate-limiter.burstCapacity: 10
key-resolver: "#{@ipKeyResolver}" # 用SpEL表达式引用配置类里的KeyResolver
这样配置完,所有访问/user/**的请求,都会被Gateway做IP级别的限流,每个IP每秒最多5个请求。
四、常见问题排查
配置完不是就万事大吉了,很多人会遇到各种问题,这里说几个最常见的:
4.1 限流不生效
这是最常见的问题,排查步骤按顺序来:
- 检查Redis连接:先看Gateway能不能连上Redis,最简单的方法是在Gateway的日志里搜“Redis”,如果有连接超时、认证失败的日志,说明连不上。也可以用Redis的客户端(比如redis-cli)直接连Gateway的Redis地址,看能不能连上。
- 检查KeyResolver:如果KeyResolver写的有问题,比如拿不到IP,限流就会按空值来,相当于所有请求都用同一个Key,这时候如果有多个IP请求,会互相影响。比如测试的时候,你用Postman发请求,KeyResolver拿到的IP可能是127.0.0.1,没问题,但如果是线上,要确保能拿到真实的客户端IP(如果Gateway前面有Nginx,需要在Nginx里配置X-Forwarded-For头,Gateway才能拿到真实IP)。
- 检查路由配置:确认限流过滤器绑对了路由,比如路由的predicates配置错了,请求没走这个路由,自然不会限流。
- 检查参数单位:注意replenishRate是每秒的令牌数,不是每分钟,很多人会搞错,比如想限每分钟300个,就写成了300,结果变成每秒300个,肯定不生效。
4.2 限流规则混乱
比如有的请求应该被限流但没限,有的不该被限却被限了,大概率是KeyResolver的问题:
- 如果是按用户ID限流,要确保每个请求都能拿到正确的用户ID,比如请求里的token解析失败,拿到的用户ID是空的,所有请求就会共用一个Key。
- 如果是按服务名限流,要确保KeyResolver是按服务名来的,比如之前的例子是按IP,如果你改成按服务名,要把KeyResolver改成拿请求的服务名。
4.3 Redis压力过大
限流用的Redis会存每个Key的令牌数,请求多了之后,Redis的QPS会很高,这时候要注意:
- 不要用太细的Key,比如按每个请求的ID限流,会导致Redis里的Key太多,占用内存大,查询慢。
- 可以用Redis的集群版,把Key分散到不同的节点,减轻单个节点的压力。
- 定期清理过期的Key,比如某个IP很久没请求了,对应的Key可以设置过期时间,Gateway的RedisRateLimiter默认会给Key加过期时间,一般不用手动处理,但如果自定义了Key,要注意设置过期时间。
五、方案的优缺点与应用场景
5.1 优缺点
优点:
- 分布式:所有Gateway实例共用Redis的限流规则,不会出现每个实例单独限流的问题。
- 性能好:Redis是内存数据库,操作速度快,不会给Gateway带来太大的性能损耗。
- 灵活:可以按不同的维度(IP、用户、服务、接口)限流,每个路由可以单独配置规则。
- 维护简单:只需要维护一个Redis实例(或集群),不用在每个服务里配置限流。
缺点:
- 依赖Redis:如果Redis挂了,限流规则就失效了,所以要保证Redis的高可用(比如用哨兵模式、集群模式)。
- 有网络延迟:Gateway和Redis之间的网络延迟会影响限流的速度,比如网络卡了,请求拿不到令牌,就会被错误拒绝。
- 不适合极端场景:如果是超大规模的流量(比如每秒百万级),Redis可能扛不住,这时候可能需要用更底层的限流组件(比如基于Nginx的限流)。
5.2 应用场景
这个方案适合大部分中大型项目的限流需求:
- 接口限流:比如登录接口、下单接口,防止被恶意刷。
- 用户限流:给每个用户设置访问频率,防止某个用户占用太多资源。
- IP限流:防止某个IP的恶意攻击。
- 热点接口限流:比如大促时的秒杀接口,限制每秒的请求数,防止系统崩溃。
六、文章总结
Spring Cloud Gateway和Redis集成的分布式限流方案,是目前中大型项目里最常用的限流方案之一,配置简单、性能好、灵活度高。核心是利用Redis的内存存储特性,实现全局的限流规则,解决了分布式架构下每个实例单独限流的问题。
配置的时候要注意几个关键点:正确引入依赖、配置好Redis连接、写对KeyResolver(限流维度)、把限流过滤器绑定到正确的路由上。排查问题的时候,按连接、Key、路由、参数的顺序来,大部分问题都能解决。
这个方案也有局限性,比如依赖Redis,不适合超大规模流量的场景,但对于90%以上的项目来说,已经足够用了。如果需要更高的性能,可以考虑结合Nginx的限流,或者用专门的限流组件(比如Sentinel),但Sentinel的分布式限流也需要依赖Redis,本质上和这个方案的逻辑是一样的。
评论
围绕“Spring Cloud Gateway与Redis集成实现分布式限流的配置细节与问题排查”参与讨论