SpringCloud Gateway的路由,过滤器和限流解读
作者:牧竹子
Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?
Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets,Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性。
Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
注意:
Spring Cloud Gateway是基于Spring Boot 2.x, Spring WebFlux和Project Reactor 构建的。
因此,在使用Spring Cloud Gateway时,许多不熟悉的同步库(例如,Spring Data和Spring Security)和模式可能不适用。
如果您对这些项目不熟悉,建议您在使用Spring Cloud Gateway之前先阅读它们的文档,以熟悉一些新概念。
Spring Cloud Gateway需要Spring Boot和Spring Webflux提供的Netty运行时。它不能在传统的Servlet容器中或作为WAR构建。
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Spring Cloud Gateway 的特征:
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 动态路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 断路器
- 集成 Spring Cloud DiscoveryClient
- 易于编写的 Predicates 和 Filters
- 限流
- 路径重写
相关概念:
- Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
- Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
- Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。
Spring Cloud Gateway 网关路由有两种配置方式:
- 在配置文件 yml 中配置通过@Bean自定义 RouteLocator,在启动主类 Application 中配置
- 这两种方式是等价的,建议使用 yml 方式进配置。
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
application.yml
server: port: 8080 spring: cloud: gateway: routes: - id: bamboo_route uri: https://blog.csdn.net/ predicates: - Path=/blogdevteam
yml方式配置路由
id
:我们自定义的路由 ID,保持唯一uri
:目标服务地址predicates
:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。filters
:过滤规则,本示例暂时没用。
启动类:代码方式配置路由
package com.bamboo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; /** * @program: spring-gateway * @description: Application * @author: Bamboo zjcjava@163.com * @create: 2019-10-26 20:00 **/ @SpringBootApplication public class Application { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() // basic proxy .route(r -> r.path("/zjcjava") .uri("https://blog.csdn.net//")) .build(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
http://localhost:8080/zjcjava
对应访问的地址是
https://blog.csdn.net/zjcjavahttp://localhost:8080/blogdevteam
对应访问的地址是
https://blog.csdn.net/blogdevteam
可以看出来,它会自动在url路径/后面加上对应的路由地址
predicates路由断言工厂
Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础架构的一部分进行匹配。Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些谓词都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以合并,也可以通过逻辑合并and。
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。网上有一张图总结了 Spring Cloud 内置的几种 Predicate 的实现。
spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true ##会使用serviceId转发到具体的服务IP上的服务 routes: - id: after_route uri: https://example.org predicates: - After=2017-01-20T17:42:47.789-07:00[America/Denver] - Before=2017-01-20T17:42:47.789-07:00[America/Denver] - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] - Cookie=chocolate, ch.p - Header=X-Request-Id, \d+ - Host=**.somehost.org,**.anotherhost.org - Method=GET - Path=/foo/{segment},/bar/{segment} - Query=baz - RemoteAddr=192.168.1.1/24 - id: weight_high uri: https://weighthigh.org predicates: - Weight=group1, 8 - id: weight_low uri: https://weightlow.org predicates: - Weight=group1, 2
spring.cloud.gateway.discovery.locator.enabled:使用eurake注册服务后:是否与服务发现组件ID进行结合,通过routes中的serviceId 转发到具体的服务实例。
默认为false,设为true便开启通过服务中心的自动根据 serviceId 创建路由的功能。
- 后路线断言工厂:After=2017年1月20日17:42山区时间(丹佛)之后的所有请求匹配
- 路线断言工厂之前:Before=2017年1月20日17:42山区时间(丹佛)之前的所有请求匹配。
- 路线断言工厂之间- Between=2017年1月20日山区时间(丹佛)之后和2017年1月21日17:42山区时间(丹佛)之后的所有请求匹配
- Cookie路线断言工厂 Cookie=请求匹配一个名为chocolatewho的值为ch.p正则表达式匹配的cookie
- 标头路由断言工厂 - Header=匹配请求具有名为X-Request-Id其值为\d+正则表达式匹配(具有一个或多个数字的值)的标头
- 主机路由断言工厂 - Host=请求的Host标头中包含值www.somehost.org或beta.somehost.org,则此路由将匹配www.anotherhost.org
- 方法路线断言工厂 - Method=此路由将匹配GET方式的请求
- 路径路线断言工厂 - Path=将匹配:/foo/1或/foo/bar或/bar/baz
- 查询路由断言工厂 - Query=匹配请求包含baz查询参数
- RemoteAddr路由断言工厂 - RemoteAddr=请求的远程地址为192.168.1.1/24之间的IP,则此路由将匹配192.168.1.10
- 权重路线断言工厂 将约80%的流量转发至weighthigh.org,并将约20%的流量转发至weightlow.org
全局过滤器
该GlobalFilter接口具有与相同的签名GatewayFilter。这些是特殊过滤器,有条件地应用于所有路由。(此界面和用法可能会在将来的里程碑中更改)。
全局过滤器和GatewayFilter的组合订购
当请求进入(并与路由匹配)时,过滤Web处理程序会将的所有实例GlobalFilter和所有特定GatewayFilter于路由的实例添加到过滤器链中。该组合的过滤器链按org.springframework.core.Ordered接口排序,可以通过实现该getOrder()方法进行设置。
由于Spring Cloud Gateway区分了执行过滤器逻辑的“前”阶段和“后”阶段(请参阅:工作原理),因此优先级最高的过滤器将在“前”阶段中处于第一个阶段,而在“后”阶段中处于最后一个阶段“-相。
ExampleConfiguration.java
//放入application启动类中main方法后面即可 @Bean public CustomGlobalFilter tokenFilter() { return new CustomGlobalFilter(); } /** * @program: spring-gateway * @description: 全局过滤器 * @author: Bamboo zjcjava@163.com * @create: 2019-10-26 23:06 **/ public class CustomGlobalFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger(CustomGlobalFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("custom global filter....................................."); // 添加全局鉴权 String token = exchange.getRequest().getQueryParams().getFirst("token"); if (token == null || token.isEmpty()) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return -1; } }
这里我创建了一个全局的鉴权过滤器,只有参数中带有token值才能继续,否则提示无权访问
http://localhost:8080/zjcjava?token=aa
fGatewayFilter工厂
路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway包括许多内置的GatewayFilter工厂。
filters配置
修改配置文件如下:抓哟是 path,filters
server: port: 8080 spring: application: name: spring-cloud-gateway cloud: gateway: routes: - id: bamboo_route uri: https://blog.csdn.net/ predicates: # - Path=/blogdevteam - Path=/test/blogdevteam filters: - StripPrefix=1 #去掉前缀 - AddResponseHeader=X-Response-Default-Foo, Default-Bar #返回消息头添加head信息
StripPrefix=1 #去掉第1个前缀以/分割
AddResponseHeader返回报文消息头添加head信息
这里只列举几个重要的过滤器
Hystrix GatewayFilter工厂
Hystrix是Netflix的一个库,用于实现断路器模式。Hystrix GatewayFilter允许您将断路器引入网关路由,保护您的服务免受级联故障的影响,并允许您在下游故障的情况下提供后备响应。
要在项目中启用Hystrix GatewayFilters,请spring-cloud-starter-netflix-hystrix从Spring Cloud Netflix添加依赖项。
Hystrix GatewayFilter工厂需要一个name参数,它是的名称HystrixCommand。
spring: cloud: gateway: routes: - id: hystrix_route uri: https://example.org filters: - Hystrix=myCommandName
这会将其余的过滤器包装在HystrixCommand带有命令名的中myCommandName。
Hystrix过滤器还可以接受可选fallbackUri参数。当前,仅forward:支持计划的URI。如果调用了后备,则请求将被转发到与URI相匹配的控制器。
限流RequestRateLimiter GatewayFilter工厂
RequestRateLimiter GatewayFilter Factory使用一种RateLimiter实现来确定是否允许继续当前请求。如果不是,HTTP 429 - Too Many Requests则返回状态(默认)。
此过滤器采用一个可选keyResolver参数和特定于速率限制器的参数(请参见下文)。
keyResolver是实现KeyResolver接口的bean 。在配置中,使用SpEL按名称引用bean。#{@myKeyResolver}是SpEL表达式,它引用名称为的bean myKeyResolver。
Redis RateLimiter
redis实现基于Stripe所做的工作。它需要使用spring-boot-starter-data-redis-reactiveSpring Boot启动器。
使用的算法是令牌桶算法。
该redis-rate-limiter.replenishRate是多么的每秒许多请求你希望用户被允许做,没有任何下降的请求。这是令牌桶被填充的速率。
redis-rate-limiter.burstCapacity是允许用户在一个单一的第二做请求的最大数目。这是令牌桶可以容纳的令牌数。将此值设置为零将阻止所有请求。
通过在replenishRate和中设置相同的值,可以达到稳定的速率burstCapacity。设置burstCapacity大于可以允许临时爆发replenishRate。在这种情况下,速率限制器需要在突发之间间隔一段时间(根据replenishRate),因为2个连续的突发将导致请求丢失(HTTP 429 - Too Many Requests)。
application.yml
spring: cloud: gateway: routes: - id: requestratelimiter_route uri: https://example.org filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20
配置文件
@Bean KeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user")); }
这定义了每个用户10的请求速率限制。允许20个并发,但是下一秒只有10个请求可用。这KeyResolver是一个简单的获取user请求参数的参数(注意:不建议在生产中使用)。
速率限制器也可以定义为实现RateLimiter接口的Bean 。在配置中,使用SpEL按名称引用bean。#{@myRateLimiter}是SpEL表达式,它引用名称为的bean myRateLimiter。
application.yml
spring: cloud: gateway: routes: - id: requestratelimiter_route uri: https://example.org filters: - name: RequestRateLimiter args: rate-limiter: "#{@myRateLimiter}" key-resolver: "#{@userKeyResolver}"
参考文档
官方参考文档:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.RC1/reference/html/
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。