Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets,Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性。
Spring Cloud Gateway是基于spring生态系统Spring 5, Spring Boot 2和Project Reactor等技术上开发的,它旨在提供简单有效的API路由服务,同时提供一些其他服务:安全、监控/指标、限流。
Spring Cloud Gateway依赖于Spring Boot和Spring Webflux提供的Netty运行时。不能再传统的Servlet容器中运行,也不能打成war包。
术语:
Route(路由):路由是网关的基础模块,它由一个ID、一个目标URI、一组断言和一组过滤器定义。当所有断言都为真时,路由匹配。
Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。
工作流程:
客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
一、Gateway的使用
1.创建gateway工程,pom.xml引入gateway,不需要引入web
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
com.kevin
gateway
0.0.1-SNAPSHOT
gateway
Demo project for Spring Boot
1.8
Greenwich.SR1
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
2. application.yml
server:
port: 9310
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: hello_route
uri: http://127.0.0.1:9210
predicates:
- Path=/sayHello,/sayHelloFeign
配置的含义:配置了一个叫hello_route的路由规则,当访问/sayHello和/sayHelloFeign自动转发到http://127.0.0.1:9210/sayHello和http://127.0.0.1:9210/sayHelloFeign
3. 启动类
package com.kevin.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
4. 测试
通过启动日志,可以看到实际启动一个netty服务
http://127.0.0.1:9310/sayHello、http://127.0.0.1:9310/sayHelloFeign都可正常访问:
二、路由断言工厂
Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。
1. 通过时间匹配:
Predicate 支持设置一个时间,在请求进行转发的时候,可以通过判断在这个时间之前或者之后进行转发。
1)After,在2019年1月1号0点后,请求都转发到http://127.0.0.1:9210,中国时区:Asia/Shanghai,与UTC时间比要加8个小时
spring:
cloud:
gateway:
routes:
- id: hello_route
uri: http://127.0.0.1:9210
predicates:
- After=2019-01-01T00:00:00+08:00[Asia/Shanghai]
2)Before
spring:
cloud:
gateway:
routes:
- id: hello_route
uri: http://127.0.0.1:9210
predicates:
- Before=2019-01-01T00:00:00+08:00[Asia/Shanghai]
3)Between
- Between=2019-01-01T00:00:00+08:00[Asia/Shanghai], 2019-12-01T00:00:00+08:00[Asia/Shanghai]
2. Cookie,请求中需要包含一个cookie参数chocolate与ch.p(正则表达式)匹配
- Cookie=chocolate, ch.p
3. Header,请求中需要Header参数X-Request-Id与\d+(一个或多个数字)匹配
- Header=X-Request-Id, \d+
4. Host,请求中需要Header参数Host与定义的地址匹配,比如www.somehost.org
- Host=**.somehost.org,**.anotherhost.org
5. Method, 可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。
- Method=GET
6. Path, 请求与指定的路径匹配时才转发,不指定路径可设置/**
- Path=/foo/{segment},/bar/{segment}
7. Query, 通过请求参数匹配,可配置两个参数,一个是属性名,一个是属性值(可选,正则表达式)
- Query=baz #请求必须要包含一个baz的参数
- Query=foo, ba. #请求必须包含一个foo的参数,其值必须是ba开头且长度为三位的字符串
8. RemoteAddr, 通过IP地址路由,如192.168.0.1/16(192.168.0.1是IP,16是掩码)
- RemoteAddr=192.168.1.1/24,
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。一个请求满足多个路由的谓词条件时,请求只会被首个成功匹配的路由转发。
三、过滤器
分为两种:GatewayFilter 与 GlobalFilter。GlobalFilter 会应用到所有的路由上,而 GatewayFilter 将应用到单个路由或者一个分组的路由上。Spring Cloud Gateway提供了24种GatewayFilter和9种GlobalFilter。
(一)GatewayFilter Factories 网关过滤工厂
1. AddRequestHeader, 往下游的请求中加入header(X-Request-Foo:Bar)
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://example.org
filters:
- AddRequestHeader=X-Request-Foo, Bar
2. AddRequestParameter, 往下游的请求中加入参数foo=bar
filters:
- AddRequestParameter=foo, bar
3. AddResponseHeader, 往下游的回复中加入header(X-Response-Foo:Bar)
filters:
- AddResponseHeader=X-Response-Foo, Bar
等等…
(二)GlobalFilter 全局过滤器,如:
1. Combined Global Filter and GatewayFilter Ordering 全局Filter和GatewayFilter组合排序
2. Forward Routing Filter 如果URL有一个forwardscheme (如 forward:///localendpoint),它将使用Spring DispatcherHandler 来处理请求。
3. LoadBalancerClient Filter 如果URL有一个lbscheme (如 lb://myservice),它将使用Spring Cloud LoadBalancerClient 将名称解析为实际主机和端口,并替换URI。
等等…
四、服务化网关
前面的demo中,通过网关访问服务,需要先指定路由规则,这显然是不友好的。Spring Cloud Gateway 提供了一种默认转发的能力,只要将 Spring Cloud Gateway 注册到服务中心,Spring Cloud Gateway 默认就会代理服务中心的所有服务。
1. pom.xml引入spring-cloud-starter-consul-discovery
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
2. application.yml去掉单个服务的配置,加入spring.cloud.gateway.discovery.locator.enabled=true,同时加入consul服务端地址
server:
port: 9310
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
#routes:
#- id: hello_route
#uri: http://127.0.0.1:9210
#predicates:
#- Path=/sayHello,/sayHelloFeign
#- Path=/**
#- After=2019-01-01T00:00:00+08:00[Asia/Shanghai]
consul:
discovery:
host: localhost
port: 8500
service-name: gateway
3. 重新启动即可
1)查看consul-ui,服务都已注册:
2)直接通过 http://网关地址:端口/服务中心注册 serviceId/具体的url 访问服务即可
五、服务化路由转发
1. 对gateway工程进行调整,application.yml增加路由规则:请求加入参数name=Kevin,对所有的consulConsumer服务有效
server:
port: 9310
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: name_route
uri: lb://consulConsumer
filters:
- AddRequestParameter=name,Kevin
predicates:
- Method=GET
consul:
discovery:
host: localhost
port: 8500
service-name: gateway
2. 对consul_consumer工程进行调整,HelloController.java中的接口增加参数,并打印当前服务端口
@Value("${server.port}")
private Integer serverPort;
@RequestMapping("/sayHelloFeign")
public String sayHelloFeign(String name){
return serverPort+ "=>" + helloFeignService.sayHello() + ", call by feign, name=" + name;
}
3. 启动一个gateway和两个consul_consumer(端口分别为9210、9211)后进行访问测试
1) 路由测试
测试结果可以看出gateway的过滤器起了作用,调用的接口路由到了consulConsumer,并传递了参数。
2)路由服务化测试
可以看出指定serviceId进行接口访问依然可以返回,但是参数却为空,说明指定serviceId访问会跳过路由规则。
六、常用过滤器之修改请求路径StripPrefix GatewayFilter Factory
在将请求发送到下游之前,要从请求中去除的路径中的节数。
routes:
- id: name_route
uri: lb://consulConsumer
filters:
- AddRequestParameter=name,Kevin
- StripPrefix=1
predicates:
- Path=/consulConsumer1/**
七、常用过滤器之限流 RequestRateLimiter GatewayFilter Factory
RequestRateLimiter使用RateLimiter实现是否允许继续执行当前请求。如果不允许继续执行,则返回HTTP 429 - Too Many Requests (默认情况下)
这个过滤器可以配置一个可选的keyResolver 参数和rate limiter参数。keyResolver 是 KeyResolver 接口的实现类.在配置中,按名称使用SpEL引用bean。
Redis RateLimiter,redis实现是基于stripe实现的,依赖spring-boot-starter-data-redis-reactive Spring Boot starter。
1)使用令牌桶算法
2)redis-rate-limiter.replenishRate 允许用户每秒执行多少请求(令牌桶的填充速率)
3)redis-rate-limiter.burstCapacity 允许用户在一秒钟内执行的最大请求数。这是令牌桶可以保存的令牌数。将此值设置为零将阻止所有请求。
稳定速率是通过在replenishRate 和 burstCapacity中设置相同的值来实现的。可通过设置burstCapacity高于replenishRate来允许临时突发流浪。在这种情况下,限流器需要在两次突发之间留出一段时间(根据replenishRate),因为连续两次突发将导致请求丢失 (HTTP 429 - Too Many Requests).。
1. pom.xml加入
org.springframework.boot
spring-boot-starter-data-redis-reactive
2. GatewayApplication.java注册KeyResolver,根据参数user来限制(不推荐生产环境使用)
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
也可以通过ip限制
exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName())
3. application.yml加入redis配置、RequestRateLimiter配置
server:
port: 9310
spring:
application:
name: gateway
redis:
host: localhost
password: 123
port: 6379
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: name_route
uri: lb://consulConsumer
filters:
- AddRequestParameter=name,Kevin
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
key-resolver: "#{@userKeyResolver}"
predicates:
- Path=/consulConsumer1/**
consul:
discovery:
host: localhost
port: 8500
service-name: gateway
八、常用过滤器之熔断 Hystrix GatewayFilter Factory
需要添加对 spring-cloud-starter-netflix-hystrix的依赖
1. pom.xml加入
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2. 创建熔断回调接口
package com.kevin.gateway;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FallbackController {
@RequestMapping("/myFallback")
public String fallback(){
return "服务不可用";
}
}
3. application.yml加入熔断配置,熔断后转发到myFallback接口
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/myFallback
九、 常用过滤器之重试 Retry GatewayFilter Factory
包括以下参数:
1. gateway加入重试配置:
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
2.将consul_consumer工程的接口进行改造,接口模拟返回500
@RequestMapping("/sayHelloFeign")
public String sayHelloFeign(String name){
// return serverPort+ "=>" + helloFeignService.sayHello() + ", call by feign, name=" + name;
System.out.println("调用feign sayHello");
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = servletRequestAttributes.getResponse();
response.setStatus(500);
return null;
}
3. 测试,调用接口返回500,后台日志打印总共调用了4次,其中重试3次
参考:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#gateway-starter
http://www.ityouknow.com/springcloud/2018/12/12/spring-cloud-gateway-start.html
https://cloud.tencent.com/developer/article/1403887