文章目录
- 令牌桶算法原理
- Gateway中限流实现
网关除了请求路由、身份验证,还有一个非常重要的作用:请求限流。当系统面对高并发请求时,为了减少对业务处理服务的压力,需要在网关中对请求限流,按照一定的速率放行请求。
常见的限流算法包括:
- 计数器算法
- 漏桶算法
- 令牌桶算法
算法介绍: https://blog.csdn.net/u012441595/article/details/102483501
令牌桶算法原理
SpringGateway中采用的是令牌桶算法,令牌桶算法原理:
- 准备一个令牌桶,有固定容量,一般为服务并发上限
- 按照固定速率,生成令牌并存入令牌桶,如果桶中令牌数达到上限,就丢弃令牌。
- 每次请求调用需要先获取令牌,只有拿到令牌,才继续执行,否则选择选择等待或者直接拒绝。
Gateway中限流实现
SpringCloudGateway是采用令牌桶算法,其令牌相关信息记录在redis中,因此我们需要安装redis,并引入Redis相关依赖。
1) 引入redis有关依赖:
<!--redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
注意:这里不是普通的redis依赖,而是响应式的Redis依赖,因为SpringGateway是基于WebFlux的响应式
2) 配置过滤条件key:
Gateway会在Redis中记录令牌相关信息,我们可以自己定义令牌桶的规则,例如:
-
给不同的请求URI路径设置不同令牌桶
-
给不同的登录用户设置不同令牌桶
-
给不同的请求IP地址设置不同令牌桶
Redis中的一个Key和Value对就是一个令牌桶。因此Key的生成规则就是桶的定义规则。SpringCloudGateway中key的生成规则定义在KeyResolver
接口中:
public interface KeyResolver {Mono<String> resolve(ServerWebExchange exchange);}
这个接口中的方法返回值就是给令牌桶生成的key。API说明:
- Mono:是一个单元素容器,用来存放令牌桶的key
- ServerWebExchange:上下文对象,可以理解为ServletContext,可以从中获取request、response、cookie等信息
比如上面的三种令牌桶规则,生成key的方式如下:
-
给不同的请求URI路径设置不同令牌桶,示例代码:
return Mono.just(exchange.getRequest().getURI().getPath());// 获取请求URI
-
给不同的登录用户设置不同令牌桶
return exchange.getPrincipal().map(Principal::getName);// 获取用户
-
给不同的请求IP地址设置不同令牌桶
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());// 获取请求者IP
这里我们选择最后一种,使用IP地址的令牌桶key。
我们定义一个类,配置一个 KeyResolve r的 Bean 实例:
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class PathKeyResolver implements KeyResolver {@Overridepublic Mono<String> resolve(ServerWebExchange exchange) {return Mono.just(exchange.getRequest().getURI().getPath());// 获取请求URI}
}
3) 配置桶参数:
另外,令牌桶的参数需要通过yaml文件来配置,参数有2个:
-
replenishRate
:每秒钟生成令牌的速率,基本上就是每秒钟允许的最大请求数量 -
burstCapacity
:令牌桶的容量,就是令牌桶中存放的最大的令牌的数量
完整配置如下:
server:port: 10010 # 网关端口
spring:application:name: gateway # 服务名称redis:host: localhostcloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 网关路由配置- id: user-server # 路由id,自定义,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址uri: lb://user-server# 路由的目标地址 lb就是负载均衡,后面跟服务名称predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/user/**- id: order-service # 路由id,自定义,只要唯一即可uri: lb://orderservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/order/**default-filters:- AddRequestHeader=name,xiaoming- name: RequestRateLimiter #请求数限流 名字不能随便写args:key-resolver: "#{@ipKeyResolver}" # 指定一个key生成器redis-rate-limiter.replenishRate: 2 # 生成令牌的速率redis-rate-limiter.burstCapacity: 4 # 桶的容量globalcors: # 全局的跨域处理........
这里配置了一个过滤器:RequestRateLimiter,并设置了三个参数:
key-resolver
:"#{@ipKeyResolver}"
是SpEL表达式,写法是#{@bean的名称}
,ipKeyResolver就是我们定义的Bean名称redis-rate-limiter.replenishRate
:每秒钟生成令牌的速率redis-rate-limiter.burstCapacity
:令牌桶的容量
这样的限流配置可以达成的效果:
- 每一个IP地址,每秒钟最多发起2次请求
- 每秒钟超过2次请求,则返回429的异常状态码
4) 测试
429:代表请求次数过多,触发限流了。