前言
已经通过案例实现了通过 Spring Cloud Gateway 网关调用服务 API,并已经了解了 Gateway 网关的几个核心概念:routes ,predicates,filters 。
这几个核心概念串起来就是 Gateway 的执行流程:客户端发请求,通过断言 predicates 进行匹配,若匹配上了则请求就会被发送到网关处理程序,并执行特定的请求过滤器链 fliters 。
Spring Cloud Gateway 提供了多种断言 predicate 工厂和过滤器 filter 工厂,也可以自定义断言工厂和过滤器工厂。
RequestRateLimiter GatewayFilter 工厂使用实现 RateLimiter 的限流器来确定当前请求是否被限流。如果被限流了,则默认返回 HTTP 429 - Too Many Requests 状态。
RequestRateLimiter 网关过滤器工厂采用可选的 keyResolver 参数和特定于速率限制器的参数。
KeyResolver 在源码中的定义:
public interface KeyResolver {
Mono<String> resolve(ServerWebExchange exchange);
}
我们可以通过 KeyResolver 来指定限流的 key ,比如可以根据用户做限流,也可以根据 IP 来做限流,或者根据接口进行限流。
基于 Redis 的限流器
默认的限流器是基于redis实现的,限流算法是大家熟悉的令牌桶(Token Bucket Algorithm),关于令牌捅的原理就不在此展开了,聪明的您看一眼下图应该就懂了:装令牌的桶容量有限,例如最多20个,令牌进入桶的速度恒定(注意,这里是和漏桶算法的区别),例如每秒10个,底部每个请求能拿到令牌才会被处理:
基于 Redis 的限流,需要引入 spring-boot-starter-data-redis-reactive 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
要实现限流,需要配置 Redis 连接以及在配置网关路由的时候添加 RequestRateLimiter 过滤器:
spring:
application:
name: circuitbreaker-gateway
# redis配置
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: path_route
uri: http://localhost:9001
predicates:
- Path=/nacos/**
filters:
- name: RequestRateLimiter
args:
# 令牌入桶的速度为每秒100个,相当于QPS
redis-rate-limiter.replenishRate: 100
# 桶内能装200个令牌,相当于峰值,要注意的是:第一秒从桶内能去200个,但是第二秒只能取到100个了,因为入桶速度是每秒100个
redis-rate-limiter.burstCapacity: 200
# 每个请求需要的令牌数
redis-rate-limiter.requestedTokens: 1
这里配置的是按照用户限流,其中参数 key-resolver: "#{@userKeyResolver}" 是一个 KeyResolver bean:
package com.example.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
@Configuration
public class CustomizeConfig {
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
}
#{@userKeyResolver} 是一个引用名为 userKeyResolver 的 bean 的 SpEL 表达式 。
其他配置项的含义:
- filter 的名称 name 必须为 RequestRateLimiter 。
- redis-rate-limiter.replenishRate :允许用户每秒处理的请求数。设置的数值就代表 每秒向令牌桶添加多少个令牌 。
- redis-rate-limiter.burstCapacity :令牌桶的容量,即允许在 1 秒内完成的最大请求数。设置为 0 则表示拒绝所有请求。
准备工作
- 为了更好的演示
Gateway的效果,在服务提供者nacos-provider的代码(NacosController.java)中新增一个web接口,可以接受一个入参:
@GetMapping("/userinfo")
public String userInfo(@RequestParam("username") String username) {
return "hello " + username + ", " + dateStr();
}
测试
下面开始测试,根据配置的路由,我们通过网关访问 http://localhost:10013/nacos/userinfo:
直接报错了,看一下异常信息:
根据
userKeyResolver 的配置,我们必须传入username 参数才能正常访问:
访问接口测试后, Redis 中会有对应的数据:
大括号中的就是限流的
key,这里是username,就是我们访问接口时传入的参数值。Redis 的 key 中还有两个定义:
- tokens:代表当前秒对应的可用的令牌数量;
- timestamp:当前时间的秒数。
再来通过 JMeter 模拟一下同时有大量请求的情况,当请求数量超过令牌桶的容量的时候,将会限流。
1. 设置每秒有 100 个请求过来:
2. 设置通过网关请求的 url,注意要带上 username 参数,这是根据用户限流的:
3. 执行,观察结果树:
当令牌桶容量满了的时候,就不允许其他请求进来了,将返回 Too Many Requests ,限流生效。