目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitHub上的星级而言)。它是作为Spring Cloud系列中Zuul代理的继任者而创建的。该项目提供了用于微服务体系结构的API网关,并基于反应式Netty和Project Reactor构建。它旨在提供一种简单而有效的方法来路由到API并解决诸如安全性,监视/度量和弹性之类的普遍关注的问题。
基于Redis限流
Spring Cloud Gateway为您提供了许多功能和配置选项。今天,我将集中讨论网关配置的一个非常有趣的方面-速率限制。速率限制器可以定义为一种控制网络上发送或接收的流量速率的方法。我们还可以定义几种类型的速率限制。Spring Cloud Gateway当前提供了一个Request Rate Limiter,它负责将每个用户每秒限制为N个请求。与Spring Cloud Gateway一起 使用时RequestRateLimiter,我们可能会利用Redis。Spring Cloud实现使用令牌桶算法做限速。该算法具有集中式存储桶主机,您可以在其中对每个请求获取令牌,然后将更多的令牌缓慢滴入存储桶中。如果存储桶为空,则拒绝该请求。
项目演示源码地址:
https://github.com/1ssqq1lxr/SpringCloudGatewayTest
- 引入maven依赖
## spring cloud依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
<spring-cloud.version>Hoxton.RC2</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
## gateway 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mockserver</artifactId>
<version>1.12.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>3.10.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.carrotsearch</groupId>
<artifactId>junit-benchmarks</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
- 限流器配置
使用Spring Cloud Gateway默认请求限流GatewayFilter(
org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter)。使用默认的Redis限流器方案,你可以通过自定义keyResolver类去决定Redis限流key的生成,下面举常用几个例子:
- 根据用户: 使用这种方式限流,请求路径中必须携带userId参数
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
- 根据获uri
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
由于我们已经讨论了Spring Cloud Gateway速率限制的一些理论方面,因此我们可以继续进行实施。首先,让我们定义主类和非常简单的KeyResolverbean,它始终等于一个。
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just("1");
}
}
Gateway默认使用
org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter 限流器。 现在,如通过模拟Http请求,则会收到以下响应。它包括一些特定的header,其前缀为x-ratelimit。
- x-ratelimit-burst-capacity:最大令牌值,
- x-ratelimit-replenish-rate:填充的速率值,
- x-ratelimit-remaining:剩下可请求数。
yaml配置:
server:
port: ${PORT:8085}
spring:
application:
name: gateway-service
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: account-service
uri: http://localhost:8091
predicates:
- Path=/account/**
filters:
- RewritePath=/account/(?.*), /$\{path}
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
- Redis限流器实现
关键源码如下:
// routeId也就是我们的服务路由id,id就是限流的key
public Mono<Response> isAllowed(String routeId, String id) {
// 会判断RedisRateLimiter是否初始化了
if (!this.initialized.get()) {
throw new IllegalStateException("RedisRateLimiter is not initialized");
}
// 获取routeId对应的限流配置
Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);
if (routeConfig == null) {
throw new IllegalArgumentException("No Configuration found for route " + routeId);
}
// 允许用户每秒做多少次请求
int replenishRate = routeConfig.getReplenishRate();
// 令牌桶的容量,允许在一秒钟内完成的最大请求数
int burstCapacity = routeConfig.getBurstCapacity();
try {
// 限流key的名称&