目錄
由於Zuul 2.x的不斷跳票,Spring Cloud自行研發了另外一款服務網關產品:Spring Cloud Gateway,並且在最新版本中推薦使用,所以Gateway出現的原因就是爲了代替Zuul。相比Zuul,Gateway是Spring體系內的產物,和Spring融合更好。同時相比於Zuul 1.x的阻塞和多線程方式,Gateway採用了Netty異步非阻塞模型,佔用資源更小,性能更有優勢。同時增加了Predicate和限流等功能。
筆者使用的Java版本是jdk-8u201,IDE使用的是IntelliJ IDEA 2019.2 x64,Spring Boot的版本是2.1.7.RELEASE,Spring Cloud的版本是Greenwich.SR2。同時本文所使用的項目代碼沿用筆者之前寫過的文章《Spring Cloud服務治理:Eureka+OpenFeign(Ribbon+Hystrix)》中的項目代碼,並在此基礎上進行繼續開發。
1 傳統路由方式
創建一個Spring Boot項目,命名爲api-gateway。
1.1 pom.xml
<?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.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hys</groupId>
<artifactId>api-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-gateway</name>
<description>Demo project for Spring Cloud</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</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>
1.2 啓動類
package com.hys.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
1.3 路由方式
Gateway的路由方式有兩種,分別爲編碼方式和配置方式。
1.3.1 編碼方式
在上面的啓動類中加入下面的自定義RouteLocator的方法即可:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("route_a", r -> r.path("/hello")
.uri("http://localhost:8081/"))
.build();
}
1.3.2 配置方式
將上面自定義RouteLocator的方法註釋掉,在application.properties文件中輸入下面的內容:
spring.application.name=api-gateway
server.port=5555
spring.cloud.gateway.routes[0].id=route_a
spring.cloud.gateway.routes[0].uri=http://localhost:8081/
spring.cloud.gateway.routes[0].predicates[0]=Path=/hello
其中後三行的內容和上述編碼配置的方式實現的效果是一樣的。
1.4 運行及結果
這裏採用配置文件的方式來運行,確保之前搭建的eureka-server、hello-service和feign-consumer項目都運行起來,啓動本項目,頁面輸入http://localhost:5555/hello,結果如下所示:
訪問http://localhost:5555/hello會被自動路由到http://localhost:8081/hello,這樣就驗證了路由轉發的成功。
2 面向服務的方式
顯而易見的是,傳統路由的配置方式比較繁瑣,如果路由特別多的情況下,維護起來會很麻煩。爲此,可以將Gateway與Eureka整合起來,這樣不用再寫具體的url映射,url交給Eureka的服務發現機制去自動維護。
2.1 pom.xml
pom沿用上面的配置,只需要再加入下面的Eureka依賴即可:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.2 application.properties
spring.application.name=api-gateway
server.port=5555
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/,http://peer3:1113/eureka/
其中spring.cloud.gateway.discovery.locator.enabled設置爲true表示開啓通過註冊中心進行路由轉發的功能,spring.cloud.gateway.discovery.locator.lowerCaseServiceId設置爲true表示通過小寫形式來訪問服務名稱。
2.3 運行及結果
重啓本項目,頁面分別訪問http://localhost:5555/feign-consumer/feign-consumer、http://localhost:5555/feign-consumer/feign-consumer2和http://localhost:5555/feign-consumer/feign-consumer3,結果如下所示:
可以看到,和之前通過OpenFeign的消費者訪問的結果是一樣的,路由轉發是成功的。
3 Predicate和Filter
Predicate和Filter是Gateway中的核心,Predicate是選擇哪些請求需要處理,而Filter給選擇出來的請求做一些改動,比如參數處理和安全校驗等等。
3.1 Predicate
新建一個Spring Boot項目,命名爲test-gateway,pom文件依賴和上述第1.1節中的pom依賴一致。這裏我們用Postman來查看運行結果。
3.1.1 時間匹配
spring.application.name=gateway-test
server.port=5556
spring.cloud.gateway.routes[0].id=route_test
spring.cloud.gateway.routes[0].uri=https://www.baidu.com/
spring.cloud.gateway.routes[0].predicates[0]=After=2019-08-12T12:00:00+08:00[Asia/Shanghai]
上述After表示在2019年8月12日12點之後的請求可以被路由,而Before代表在指定時間之前可以被路由,Between則代表在指定的時間區隔之內可以被路由:
After=2019-08-12T12:00:00+08:00[Asia/Shanghai]
Before=2019-08-12T12:00:00+08:00[Asia/Shanghai]
Between=2019-08-12T12:00:00+08:00[Asia/Shanghai], 2019-08-13T12:00:00+08:00[Asia/Shanghai]
3.1.2 請求方式匹配
spring.application.name=gateway-test
server.port=5556
spring.cloud.gateway.routes[0].id=route_test
spring.cloud.gateway.routes[0].uri=https://www.baidu.com/
spring.cloud.gateway.routes[0].predicates[0]=Method=GET
上述表示只有GET請求才能被成功路由,訪問Postman得到如下結果:
狀態碼爲200,說明成功訪問,這時我們改成POST請求,再來訪問:
狀態碼爲404,說明訪問失敗。
3.1.3 請求路徑匹配
spring.application.name=gateway-test
server.port=5556
spring.cloud.gateway.routes[0].id=route_test
spring.cloud.gateway.routes[0].uri=https://www.baidu.com/
spring.cloud.gateway.routes[0].predicates[0]=Path=/foo/{segment}
由上配置了匹配的請求路徑,Postman訪問http://localhost:5556/foo/1,訪問成功:
訪問http://localhost:5556/foo/1/2,訪問失敗:
3.1.4 請求參數匹配
spring.application.name=gateway-test
server.port=5556
spring.cloud.gateway.routes[0].id=route_test
spring.cloud.gateway.routes[0].uri=https://www.baidu.com/
spring.cloud.gateway.routes[0].predicates[0]=Query=p1
上述配置了請求參數中必須含有p1參數才能路由成功,Postman訪問http://localhost:5556/?p1=1,路由成功:
訪問http://localhost:5556/?p2=2,路由失敗:
Query的值還可以使用正則表達式來進行匹配,如下面的例子:
spring.application.name=gateway-test
server.port=5556
spring.cloud.gateway.routes[0].id=route_test
spring.cloud.gateway.routes[0].uri=https://www.baidu.com/
spring.cloud.gateway.routes[0].predicates[0]=Query=p1, 1.
上面配置了參數中的鍵必須含有p1,同時它所對應的值是以1開頭的兩個字符,Postman訪問http://localhost:5556/?p1=1s,路由成功:
Postman訪問http://localhost:5556/?p1=1,路由失敗:
3.2 Filter
這裏只演示AddRequestParameter的用法,更多的用法詳見Spring官網。
AddRequestParameter是在請求的路徑中添加相應的參數,我們繼續使用上述的api-gateway項目。
3.2.1 hello-service
首先需要對之前的hello-service項目做些更改,在其中的HelloController中添加一個foo方法如下所示:
@RequestMapping("/foo")
public String foo(String foo) {
return foo;
}
3.2.2 feign-consumer
然後在feign-consumer項目中的ConsumerController中添加下面的方法:
@RequestMapping("/foo")
public String foo(String foo) {
return helloService.foo(foo);
}
在IHelloService中添加下面的方法:
@RequestMapping("/foo")
String foo(@RequestParam("foo") String foo);
在相應的HelloServiceImplFallback降級類中填入下面的降級方法:
@Override
public String foo(String foo) {
return "訪問超時,請重新再試!";
}
3.2.3 application.properties
api-gateway網關的配置文件需要做些修改:
spring.application.name=api-gateway
server.port=5555
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
spring.cloud.gateway.routes[0].id=add_request_parameter_route
spring.cloud.gateway.routes[0].uri=lb://hello-service
spring.cloud.gateway.routes[0].predicates[0]=Method=GET
spring.cloud.gateway.routes[0].filters[0]=AddRequestParameter=foo, bar
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/,http://peer3:1113/eureka/
其中,uri表示配置路由轉發到hello-service的服務提供者。filters表示給匹配的請求中添加了一個foo=bar的參數。需要注意的是,filters必須和predicates聯用,否則項目啓動會失敗。
3.2.4 運行及結果
隨後分別啓動eureka-server、hello-service和feign-consumer項目,然後啓動api-gateway網關項目,首先頁面訪問http://localhost:9001/foo,以OpenFeign消費者的方式來訪問服務:
由上可以看到,當沒有使用網關來訪問服務的時候,頁面上沒有結果,也就是說服務提供者沒有接收到foo參數。然後我們訪問http://localhost:5555/foo,以網關的方式來訪問服務:
以上可知,頁面上顯示了bar,foo參數被成功接收,在請求中會添加一個foo=bar的參數。