网关Gateway(3.0.1)入门:配置过滤器分布式限流
Posted 可可托海-我大爱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网关Gateway(3.0.1)入门:配置过滤器分布式限流相关的知识,希望对你有一定的参考价值。
Spring Cloud Gateway(3.0.1)
This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.(安全性、监控/度量和恢复能力)
1. How to Include Spring Cloud Gateway
To include Spring Cloud Gateway in your project, use the starter with a group ID of org.springframework.cloud and an artifact ID of spring-cloud-starter-gateway. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
If you include the starter, but you do not want the gateway to be enabled, set spring.cloud.gateway.enabled=false.
解读
maven依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
导入maven依赖了,但不想使用:spring.cloud.gateway.enabled=false
Spring Cloud Gateway is built on Spring Boot 2.x, Spring WebFlux, and Project Reactor. As a consequence, many of the familiar synchronous libraries (Spring Data and Spring Security, for example) and patterns you know may not apply when you use Spring Cloud Gateway. If you are unfamiliar with these projects, we suggest you begin by reading their documentation to familiarize yourself with some of the new concepts before working with Spring Cloud Gateway.
在开始Spring Cloud Gateway前,你应该了解前置知识:spring boot、Spring webFlux、Project Reactor、Spring Data and Spring Security等知识,如果你不熟悉,建议你开始前,先阅读这些知识。
Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or when built as a WAR.
Spring Cloud Gateway 需要 Netty和Spring Webflux环境、在传统的Servlet Container是不能工作的。
2. Glossary(词汇表)
- Route: The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates, and a collection of filters. A route is matched if the aggregate predicate is true.
- Predicate: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.
- Filter: These are instances of GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.
3. How It Works
The following diagram provides a high-level overview of how Spring Cloud Gateway works:
Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.
4. Configuring Route Predicate Factories and Gateway Filter Factories
There are two ways to configure predicates and filters: shortcuts and fully expanded arguments. Most examples below use the shortcut way.
The name and argument names will be listed as code
in the first sentance or two of the each section. The arguments are typically listed in the order that would be needed for the shortcut configuration.
4.1. Shortcut Configuration
Shortcut configuration is recognized by the filter name, followed by an equals sign (=
), followed by argument values separated by commas (,
).
application.yml
server:
port: 7070
# 指定eureka注册中心
eureka:
instance:
instance-id: cloud-gateway-test-7070 #显示此名字(默认是当前项目http://localhost:7070)
prefer-ip-address: true #访问路径可以显示ip地址
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #注册中心地址
spring:
application:
name: cloud-gateway-test #注册的注册中心的服务名
cloud:
gateway:
discovery:
locator:
enabled: true #开启网关
routes:
- id: baidu-id #路由id,保持唯一
uri: https://www.baidu.com #目标地址
predicates:
- Cookie=mycookie,mycookievalue
The previous sample defines the Cookie
Route Predicate Factory with two arguments, the cookie name, mycookie
and the value to match mycookievalue
.
4.2.Fully Expanded Arguments
Fully expanded arguments appear more like standard yaml configuration with name/value pairs. Typically, there will be a name
key and an args
key. The args
key is a map of key value pairs to configure the predicate or filter.
spring:
application:
name: cloud-gateway-test #注册的注册中心的服务名
cloud:
gateway:
discovery:
locator:
enabled: true #开启网关
routes:
- id: baidu-id #路由id,保持唯一
uri: https://www.baidu.com #目标地址
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
This is the full configuration of the shortcut configuration of the Cookie
predicate shown above.
5. Route Predicate Factories
Spring Cloud Gateway matches routes as part of the Spring WebFlux HandlerMapping
infrastructure. Spring Cloud Gateway includes many built-in route predicate factories. All of these predicates match on different attributes of the HTTP request. You can combine multiple route predicate factories with logical and
statements.
5.1. The After Route Predicate Factory
The After
route predicate factory takes one parameter, a datetime
(which is a java ZonedDateTime
). This predicate matches requests that happen after the specified datetime. The following example configures an after route predicate:
spring:
application:
name: cloud-gateway-test #注册的注册中心的服务名
cloud:
gateway:
discovery:
locator:
enabled: true #开启网关
routes:
- id: baidu-id #路由id,保持唯一
#服务器
uri: https://blog.csdn.net #目标地址 https://localhost:7070,https://blog.csdn.net/qq_38930804?spm=1001.2101.3001.5343
predicates:
- Path=/qq_38930804 #断言,当客户端输入:http://localhost:7070/ 会转发到 :https://www.baidu.com
#- After=2017-01-20T17:42:47.789-07:00[America/Denver] #在这个时间后访问
#- Before=2017-01-20T17:42:47.789-07:00[America/Denver] #在这个时间之前访问
#在这个时间段可以访问
#- Between=2021-01-20T17:42:47.789-07:00[America/Denver],2021-12-20T17:42:47.789-07:00[America/Denver]
#- Header=token,c248961d-dc11-31e5-943b-a96a62310fd6 #请求头token=c248961d-dc11-31e5-943b-a96a62310fd6
#- Query=smile #请求参数含有smile属性
#- Query=smile,good. #请求参数属性smile的值以good+任意一个字符
#- Host=localhost #host=localhost的可以访问
#- Method=GET #请求方法是GET
- RemoteAddr=172.16.0.22/24 #请求ip匹配
5.11. The Weight Route Predicate Factory
The Weight
route predicate factory takes two arguments: group
and weight
(an int). The weights are calculated per group. The following example configures a weight route predicate:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
This route would forward ~80% of traffic to weighthigh.org and ~20% of traffic to weighlow.org
5.过滤器
5.1.全局过滤器
步骤:1、编写配置类实现GlobalFilter, Ordered接口,2、把配置类注入到spring容器
@Configuration
public class GlobalGateWayFilter {
// 把过滤器注入到spring容器
@Bean
@Order(-1)
public GlobalFilter authFilter(){
return new AuthFilter();
}
// 实现GlobalFilter,Ordered
public class AuthFilter implements GlobalFilter, Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 请求进入
System.err.println("pre.......................");
return chain.filter(exchange).then(Mono.fromRunnable(() ->
{
// 返回之前执行
System.err.println("post.................");
}));
}
// 值越小,优先级越高,越早执行
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 1;
}
}
}
5.2.局部过滤器
步骤:1.编写配置类实现GatewayFilter, Ordered 2.加入到过滤器工厂,并且注册到spring容器中 3、配置文件yml中配置过滤器
@Slf4j
@Component
public class UserIdCheckGateWayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getPath().pathWithinApplication().value();
log.info("请求URL:" + url);
log.info("请求方法:" + exchange.getRequest().getMethod());
//获取param 请求参数
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
log.info("uname:"+uname);
//获取header
String userId = exchange.getRequest().getHeaders().getFirst("user-id");
log.info("userId:" + userId);
if (StringUtils.isBlank(userId))
{
log.info("*****头部验证不通过,请在头部输入 user-id");
//终止请求,直接回应
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
return bufferFactory.wrap("please input user-id".getBytes());
}));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE+1;
}
}
/**
* 加入到过滤器工厂,并且注册到spring容器中
*/
@Component
public class UserIdCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
@Override
public GatewayFilter apply(Object config)
{
return new UserIdCheckGateWayFilter();
}
}
filters: #过滤器,请求与响应的过滤
- UserIdCheck #自定过滤器
配置汇总
server:
port: 7070
# 指定eureka注册中心
eureka:
instance:
instance-id: cloud-gateway-test-7070 #服务显示此名字,不指定默认spring.application.name
prefer-ip-address: true #访问路径可以显示ip地址
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #注册中心地址
spring:
application:
name: cloud-gateway-test #注册的注册中心的服务名
redis:
host: 127.0.0.1
database: 0
port: 6379
password: xxxxxx
cloud:
gateway:
discovery:
locator:
enabled: true #开启网关
globalcors:
cors-configurations: #跨域问题
\'[/**]\':
allowCredentials: true
allowedHeaders: "allowedOriginPatterns"
allowedMethods: "allowedOriginPatterns"
allowedOrigins: "allowedOriginPatterns"
routes:
- id: cloud-provider-id #路由id,保持唯一
uri: lb://cloud-provider #目标地址 https://localhost:7070,https://blog.csdn.net/qq_38930804?spm=1001.2101.3001.5343
predicates:
- Path=/hello/** #请求路径是/hello/**
#- After=2017-01-20T17:42:47.789-07:00[America/Denver] #在这个时间后访问
#- Before=2017-01-20T17:42:47.789-07:00[America/Denver] #在这个时间之前访问
#在这个时间段可以访问
#- Between=2021-01-20T17:42:47.789-07:00[America/Denver],2021-12-20T17:42:47.789-07:00[America/Denver]
#- Header=token,c248961d-dc11-31e5-943b-a96a62310fd6 #请求头token=c248961d-dc11-31e5-943b-a96a62310fd6
#- Query=smile #请求参数含有smile属性
#- Query=smile,good. #请求参数属性smile的值以good+任意一个字符
#- Host=localhost #host=localhost的可以访问
#- Method=GET #请求方法是GET
#- RemoteAddr=172.16.0.22/24 #请求ip匹配
filters: #过滤器,请求与响应的过滤
- UserIdCheck #自定过滤器
- name: RequestRateLimiter #限流过滤器
args:
#用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
key-resolver: \'#{@apiKeyResolver}\'
redis-rate-limiter.replenishRate: 2 #令牌桶每秒填充平均速率。
redis-rate-limiter.burstCapacity: 10 #令牌桶总容量。
6.分布式限流
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现上述三种过滤器。但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用在Redis内的通过执行Lua脚本实现了令牌桶的方式。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中
org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory
6.1.首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
6.2.编写application.yml
spring:
application:
name: cloud-gateway-test #注册的注册中心的服务名
redis:
host: 127.0.0.1
database: 0
port: 6379
password: xxxxxx
cloud:
gateway:
discovery:
locator:
enabled: true #开启网关
globalcors:
cors-configurations: #跨域问题
\'[/**]\':
allowCredentials: true
allowedHeaders: "allowedOriginPatterns"
allowedMethods: "allowedOriginPatterns"
allowedOrigins: "allowedOriginPatterns"
routes:
- id: cloud-provider-id #路由id,保持唯一
uri: lb://cloud-provider #目标地址 https://localhost:7070,https://blog.csdn.net/qq_38930804?spm=1001.2101.3001.5343
predicates:
- Path=/hello/** #请求路径是/hello/**
filters: #过滤器,请求与响应的过滤
- name: RequestRateLimiter #限流过滤器
args:
#用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
key-resolver: \'#{@apiKeyResolver}\'
redis-rate-limiter.replenishRate: 2 #令牌桶每秒填充平均速率。
redis-rate-limiter.burstCapacity: 10 #令牌桶总容量。
配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:
- burstCapacity,令牌桶总容量。
- replenishRate,令牌桶每秒填充平均速率。
- key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
6.3.编写限流方式
@Configuration
public class RateConfig {
// 根据用户ip限流
//@Bean()
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
// 根据URI进行限流
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
// 根据请求头的user-id限流
//@Bean
KeyResolver headerUserIdResolver(){
return exchange->Mono.just(exchange.getRequest().getHeaders().getFirst("user-id"));
}
}
以上是关于网关Gateway(3.0.1)入门:配置过滤器分布式限流的主要内容,如果未能解决你的问题,请参考以下文章
Gateway网关(快速入门断言工厂过滤器工厂全局过滤器),解决跨域问题