常见的限流算法主要有四种,它们控制流量进入系统的逻辑各不相同,也各有适用的场景。
1. 固定窗口计数器
工作原理:将时间划分为固定的时间窗口(如1秒),在每个窗口内维护一个计数器。每来一个请求,计数器加1。若计数超过阈值(如100),则拒绝后续请求。窗口结束后,计数器清零。
优点:实现非常简单,内存占用极低。
缺点 :存在临界突变问题。例如,窗口1秒限100请求,若前100个请求集中在第0.9-1.0秒,后100个请求集中在第1.0-1.1秒,系统在0.2秒内承受了200个请求,会瞬间超载。
2. 滑动窗口计数器
工作原理:将固定窗口再细分为多个小格子(比如1秒窗口,分为10个100ms的格子)。滑动窗口随着时间向右移动,计算当前时间点所处的整个窗口内所有格子的请求总和。总和超过阈值则限流。
优点:解决了固定窗口的临界突变问题,限流更平滑,精度较高(格子越细越准)。
缺点:实现复杂度比固定窗口高,需要维护多个计数器,占用更多内存。
3. 漏桶算法
工作原理 :想象一个底部有个小洞的桶。请求(水滴)以任意速率流入桶中,然后以恒定的速率从桶底流出被处理。如果桶满了,新请求就会被丢弃(溢出)。
优点:输出流量绝对平稳,能够强行整形突发流量,保护下游系统不被任何瞬时高峰冲垮。
缺点 :无法应对突发流量。即使系统当下有多余容量,也无法快速处理积压的请求,可能导致不必要的延迟。对需要允许一定突发的业务不太友好。
4. 令牌桶算法
工作原理 :桶中按固定速率(如每秒10个)生成令牌。请求到来时,必须从桶中获取一个令牌,才能被处理。如果桶中没有令牌,请求就被拒绝。桶中可以积攒一定数量的令牌(即 burstCapacity 突发容量),用于应对短时突发流量。
优点 :既保证了平均速率,又允许一定程度的突发(只要令牌积攒足够)。突发流量不会立刻导致限流,而是消耗积攒的令牌,之后又会以恒定速率补充。这也是生产环境(如Spring Cloud Gateway)中最常用的算法。
缺点:实现相对复杂(通常依赖Redis或本地内存存储令牌数),且需要正确处理并发。
快速对比与选择建议
| 算法 | 平稳流量 | 允许突发 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|
| 固定窗口计数器 | 差 | 是(但有边界突变) | 极低 | 简单粗暴的限流,不关心瞬间尖刺 |
| 滑动窗口计数器 | 较好 | 是 | 中等 | 对精度有要求,允许平滑突发 |
| 漏桶 | 绝对平稳 | 否 | 中等 | 必须强制下游以恒定速率处理,如数据库写入 |
| 令牌桶 | 平稳 | 是(可控) | 较高 | 通用首选,API网关、业务接口限流 |
5.Spring Gateway限流
在 Spring Cloud Gateway 中,最直接、最推荐的限流方案是使用框架内置的 RequestRateLimiter 过滤器工厂,并配合基于 Redis 的 RedisRateLimiter 来实现分布式限流。
Spring Cloud Gateway 默认使用的令牌桶算法(Token Bucket Algorithm),也是一种允许一定突发流量的算法。除此之外,常见的限流算法还有简单但有"突刺现象"的计数器算法,以及能平滑流量但无法应对突发情况的漏桶算法。
下面是基于Redis的限流实现:
5.1. 添加依赖
在你的 pom.xml 文件中添加 spring-boot-starter-data-redis-reactive 依赖。
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
5.2. 配置 Redis 连接信息
在 application.yml 中配置 Redis 连接信息。
yaml
spring:
redis:
host: localhost
port: 6379
# password: yourpassword # 如有密码需配置
5.3. 配置限流规则
在网关路由配置中,为需要限流的 routes 添加 RequestRateLimiter 过滤器。
yaml
spring:
cloud:
gateway:
routes:
- id: your-service-id
uri: lb://your-service-name
predicates:
- Path=/api/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
key-resolver: 定义限流规则的粒度(如按用户ID、IP、URI等),下文详解。redis-rate-limiter.replenishRate: 令牌桶填充速率,即每秒允许通过的请求数。redis-rate-limiter.burstCapacity: 令牌桶总容量,即系统能够处理的瞬时最大请求数,也是令牌桶可以累积的令牌数。redis-rate-limiter.requestedTokens: 每次请求消耗的令牌数 ,默认值1。
5.4. 自定义限流维度
通过实现 KeyResolver 接口,定义限流的Key,从而实现不同的限流策略。
- 按用户ID限流
java
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 这里假设userId存放在请求头中,你可以根据实际情况调整
String userId = exchange.getRequest().getHeaders().getFirst("userId");
return Mono.just(userId != null ? userId : "anonymous");
};
}
- 按IP地址限流
java
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
return Mono.just(ip);
};
}
- 按请求路径限流
java
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
- 组合限流
java
@Bean
public KeyResolver combinedKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("userId");
String path = exchange.getRequest().getPath().toString();
return Mono.just((userId != null ? userId : "anonymous") + ":" + path);
};
}
在实际应用中,令牌桶 因其既能平滑流量又能应对突发,成为最主流的限流算法(如Spring Cloud Gateway、Guava RateLimiter、Nginx的 limit_req 模块均采用或类似令牌桶)。漏桶算法则常用于强制流量整形的场景。