网关除了请求路由、身份验证,还有一个非常重要的作用:请求限流。当系统面对高并发请求时,为了减少对业务处理服务的压力,需要在网关中对请求限流,按照一定的速率放行请求。
常见的限流算法包括:
- 计数器算法
- 漏桶算法
- 令牌桶算法
算法介绍: 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 { @Override public 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: localhost cloud: 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:代表请求次数过多,触发限流了。