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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kevin</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
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加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
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
4. 一秒内頻繁通路報429錯誤
八、常用過濾器之熔斷 Hystrix GatewayFilter Factory
需要添加對 spring-cloud-starter-netflix-hystrix的依賴
1. pom.xml加入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
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
4. 停掉服務用戶端,隻啟動網關
九、 常用過濾器之重試 Retry GatewayFilter Factory
包括以下參數:
- retries: 重試次數,預設值是3次
- statuses: 應該重試的HTTP狀态代碼,用org.springframework.http.HttpStatus辨別
- methods: 應該重試的HTTP方法,用 org.springframework.http.HttpMethod辨別,預設GET
-
series: 要重試的一系列狀态碼,用
org.springframework.http.HttpStatus.Series辨別,預設是SERVER_ERROR(5),即5xx狀态碼
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