[TOC]html
簡介:java
Spring Cloud Gateway是Spring Cloud體系的第二代網關組件,基於Spring 5.0的新特性WebFlux進行開發,底層網絡通訊框架使用的是Netty,因此其吞吐量高、性能強勁,將來將會取代第一代的網關組件Zuul。Spring Cloud Gateway能夠經過服務發現組件自動轉發請求,默認集成了Ribbon作負載均衡,以及默認使用Hystrix對網關進行保護,固然也能夠選擇其餘的容錯組件,例如Sentinelnode
優勢:react
- 性能強勁:是第一代網關Zuul的1.6倍
- 功能強大:內置了不少實用的功能,例如轉發、監控、限流等
- 設計優雅,容易擴展
缺點:web
- 其實現依賴Netty與WebFlux,不是傳統的Servlet編程模型,有必定的學習成本
- 不能在Servlet容器下工做,也不能構建成WAR包,即不能將其部署在Tomcat、Jetty等Servlet容器裏,只能打成jar包執行
- 不支持Spring Boot 1.x,需2.0及更高的版本
若是對網關概念或Zuul不瞭解的話,能夠參考另外一篇文章:spring
核心概念:編程
一、Route(路由):小程序
Spring Cloud Gateway的基礎元素,可簡單理解成一條轉發規則。包含:ID、目標URL、Predicate集合以及Filter集合瀏覽器
這是一段比較典型的Gateway路由配置:網絡
spring: cloud: gateway: routes: - id: user-center # 惟一標識,一般使用服務id uri: lb://user-center # 目標URL,lb表明從註冊中心獲取服務,lb是Load Balance的縮寫 predicates: # Predicate集合 - Path=/zj/cloud/v1/user-center/** # 匹配轉發路徑 filters: # Filter集合 - StripPrefix=4 # 從第幾級開始轉發
二、Predicate(謂詞):
即
java.util.function.Predicate
這個接口,Gateway使用Predicate實現路由的匹配條件
三、Filter(過濾器):
與咱們平時使用的Servlet編程模型裏的過濾器概念相似,一樣能夠用於修改請求以及響應數據,能夠利用Filter實現鑑權、訪問日誌記錄,接口耗時記錄等功能
Spring Cloud Gateway架構圖:
簡單解讀一下這個圖:
Gateway Client發送請求給Spring Cloud Gateway,Gateway Handler Mapping會判斷請求的路徑是否匹配路由的配置,若是匹配則會進入Gateway Web Handler,Web Handler會讀取路由上所配置的過濾器,而後將該請求交給過濾器去處理,最後轉發到路由配置的微服務上
- Gateway Client:泛指外部請求,例如瀏覽器、app、小程序等
- Proxied Service:指的是被網關代理的微服務
相關源碼:
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping
org.springframework.cloud.gateway.handler.FilteringWebHandler
因爲Webflux大量運用函數式編程思想,因此本文中的示例代碼都會使用lambda表達式及函數式API來簡化。若對此不瞭解的話,能夠參考相關文章,篇幅有限這裏就不進行介紹了:
這裏使用IDEA的Spring Initializr進行項目的建立,到選擇依賴這一步勾選gateway依賴,以下圖:
網關組件通常都配合服務發現組件使用,我這裏使用Nacos做爲服務發現組件,具體的依賴以下:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- Nacos Client --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <!--整合Spring Cloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> <!--整合Spring Cloud Alibaba--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
若是對Nacos不熟悉的話能夠參考另外一篇關於Nacos的文章,或者採用Eureka也是同樣的:
而後編寫配置文件內容以下:
server: port: 8040 spring: application: name: gateway cloud: nacos: discovery: # 指定nacos server的地址 server-addr: 127.0.0.1:8848 gateway: discovery: locator: # 讓gateway經過服務發現組件找到其餘的微服務,從而自動轉發請求 enabled: true # actuator相關配置 management: endpoints: web: exposure: # 暴露全部監控端點 include: '*' endpoint: health: # 老是顯示健康檢測詳情 show-details: always
完成以上步驟後,咱們來啓動這個網關服務,進行一個簡單的測試,看看是否能將請求正常地轉發到指定的微服務上。此時有一個名爲user-center
的微服務,該微服務有一個按id獲取用戶信息的接口,接口路徑爲/users/{id}
。若經過網關服務來訪問這個接口,要如何作呢?很簡單,gateway配合服務發現組件使用時,會有一個默認的轉發規則,以下:
${GATEWAY_URL}/{微服務名稱}/{接口路徑}
因此按該規則得出來的具體url爲:localhost:8040/user-center/users/{id}
,訪問結果以下:
從測試結果能夠看到,gateway能夠根據url上的微服務名稱將訪問請求轉發到該微服務上。
以上這種是Gateway最簡單的使用方式,但一般在實際開發中,可能不但願使用默認的轉發規則,由於這種方式不太靈活,例如一些服務接口是存在版本劃分的,須要根據不一樣版本的訪問路徑轉發到不一樣版本的微服務上。此時就須要自定義轉發路由,實際上在第一小節的時候就已經給出過配置示例了。修改配置以下:
spring: cloud: gateway: routes: - id: user-center # 惟一標識,一般使用服務id uri: lb://user-center # 目標URL,lb表明從註冊中心獲取服務 predicates: # Predicate集合 - Path=/zj/cloud/v1/user-center/** # 匹配轉發路徑 filters: # Filter集合 - StripPrefix=4 # 從第幾級開始轉發,數字從0開始
自定義路由的注意事項:
predicates
配置項必須有,且必須配置一個及以上的Predicate
,但不必定非要配置Path
,能夠配置其餘的Predicate
,例如After
、Before
等,此時Path
的默認值爲/**
重啓項目,此時訪問的url爲:localhost:8040/zj/cloud/v1/user-center/users/{id}
,訪問結果以下:
Spring Cloud Gateway的路由配置有兩種形式,分別是路由到指定的URL以及路由到指定的微服務,在上一小節的示例中咱們就已經使用過路由到微服務的這種配置形式了。在這兩種形式中,均支持訪問路徑的通配及精確匹配,在以前的示例中咱們只使用了通配。因此本小節將給出具體的配置示例,以此直觀的瞭解這兩種形式及不一樣匹配方式在配置上的區別。
通配,使用通配符/**
進行匹配,示例:
spring: cloud: gateway: routes: - id: test_route # 路由的惟一標識 uri: http://www.xxx.com predicates: # 使用通配符匹配 - Path=/**
GATEWAY_URL/**
時會轉發到 http://www.xxx.com/**
精確匹配,配置具體的接口路徑便可,示例:
spring: cloud: gateway: routes: - id: test_route # 路由的惟一標識 uri: http://www.xxx.com/user/order/detail predicates: # 指定具體的路徑進行匹配 - Path=/user/order/detail
GATEWAY_URL/user/order/detail
時會轉發到 http://www.xxx.com/user/order/detail
通配,示例:
spring: cloud: gateway: routes: - id: user-center # 路由的惟一標識,這種形式下一般是微服務名稱 uri: lb://user-center # lb表明從註冊中心獲取服務 predicates: # 使用通配符匹配 - Path=/**
GATEWAY_URL/**
時會轉發到 user-center
微服務的/**
精確匹配,示例:
spring: cloud: gateway: routes: - id: user-center # 路由的惟一標識,這種形式下一般是微服務名稱 uri: lb://user-center/users/info # lb表明從註冊中心獲取服務 predicates: # 指定具體的路徑進行匹配 - Path=/users/info
GATEWAY_URL/users/info
時會轉發到 user-center
微服務的/users/info
前面提到過謂詞是路由的判斷條件,而路由謂詞工廠就是做用到指定路由上的一堆謂詞判斷條件。在以前的示例裏,咱們就已經使用過路由謂詞工廠了,就是自定義轉發路徑時所配置的Path。
Spring Cloud Gateway內置了衆多路由謂詞工廠,這些路由謂詞工廠爲路由匹配的判斷提供了有力的支持,而咱們以前所使用的Path就是內置的路由謂詞工廠之一,用於判斷當前訪問的接口路徑是否與該路由所配置的路徑相匹配,若匹配則進行轉發。因爲Gateway內置的路由謂詞工廠比較多,篇幅有限就不在本文中介紹了,能夠參考另外一篇文章:
如今咱們已經知道Spring Cloud Gateway內置了一系列的路由謂詞工廠,但若是這些內置的路由謂詞工廠不能知足業務需求的話,咱們能夠自定義路由謂詞工廠來實現特定的需求。例若有某個服務限制用戶只容許在09:00 - 17:00這個時間段內才能夠訪問,內置的路由謂詞工廠是沒法知足這個需求的,因此此時咱們就須要自定義可以實現該需求的路由謂詞工廠。
首先定義一個配置類,用於承載時間段的配置參數:
@Data public class TimeBetweenConfig { /** * 開始時間 */ private LocalTime start; /** * 結束時間 */ private LocalTime end; }
而後定義一個路由謂詞工廠,具體代碼以下:
package com.zj.node.gateway.predicate; import com.zj.node.gateway.config.TimeBetweenConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.time.LocalTime; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; /** * 路由謂詞工廠必須以RoutePredicateFactory結尾, * 這是Spring Cloud Gateway的約定 * * @author 01 * @date 2019-08-14 **/ @Slf4j @Component public class TimeBetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<TimeBetweenConfig> { public TimeBetweenRoutePredicateFactory() { super(TimeBetweenConfig.class); } /** * 實現謂詞判斷的方法 */ @Override public Predicate<ServerWebExchange> apply(TimeBetweenConfig config) { return exchange -> { LocalTime start = config.getStart(); LocalTime end = config.getEnd(); // 判斷當前時間是否爲容許訪問的時間段內 LocalTime now = LocalTime.now(); return now.isAfter(start) && now.isBefore(end); }; } /** * 控制配置類(TimeBetweenConfig)屬性和配置文件中配置項(TimeBetween)的映射關係 */ @Override public List<String> shortcutFieldOrder() { /* * 例如咱們的配置項是:TimeBetween=上午9:00, 下午5:00 * 那麼按照順序,start對應的是上午9:00;end對應的是下午5:00 **/ return Arrays.asList("start", "end"); } }
最後須要在配置文件中啓用該路由謂詞工廠,而且須要禁止gateway經過服務發現組件轉發請求到其餘的微服務,修改Gateway相關配置以下:
spring: cloud: gateway: discovery: locator: # 禁止gateway經過服務發現組件轉發請求到其餘的微服務 enabled: false routes: - id: user-center # 目標URL,lb表明從註冊中心獲取服務 uri: lb://user-center predicates: # 注意名稱必須爲路由謂詞工廠類名的前綴,參數爲容許訪問的時間段 - TimeBetween=上午9:00,下午5:00
能夠看到這裏主要是配置了咱們自定義的路由謂詞工廠類名的前綴以及容許訪問的時間段,這個時間格式不是隨便配置的,而是Spring Cloud Gateway的默認時間格式,相關源碼以下:
org.springframework.format.support.DefaultFormattingConversionService#addDefaultFormatters
時間格式是能夠註冊的,關於時間格式註冊的相關源碼以下:
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar#registerFormatters
另外,這裏之因此要禁止gateway經過服務發現組件轉發請求到其餘的微服務,是由於開啓該配置項的話會致使咱們自定義的路由謂詞工廠不生效。不生效也是有緣由的,開啓該配置項會令Gateway優先將請求按照該配置項進行轉發,那麼咱們自定義的路由就不會生效。
到此爲止咱們就實現了一個自定義路由謂詞工廠,若此時不在容許的訪問時間段內,訪問就會報404,以下:
前面提到了過濾器能夠爲請求和響應添加一些業務邏輯或者修改請求和響應對象等,適當地使用過濾器可讓咱們的工做事半功倍,而本小節將要介紹的過濾器工廠就是用來建立過濾器的。在此以前咱們已經學習過路由謂詞工廠了,而過濾器工廠與路由謂詞工廠在使用上是相似的,只不過實現的功能不同。
一樣的Spring Cloud Gateway內置了很是多的過濾器工廠,有二十多個。經過這些內置的過濾器工廠就已經能夠靈活且方便地處理請求和響應數據,因爲Gateway內置的過濾器工廠實在太多,而篇幅有限就不在本文中介紹了,能夠參考另外一篇文章:
若Spring Cloud Gateway內置的過濾器工廠沒法知足咱們的業務需求,那麼此時就須要自定義本身的過濾器工廠以實現特定功能。所謂過濾器工廠實際上就是用於建立過濾器實例的,而建立的過濾器實例都實現於GatewayFilter
接口。
過濾器的生命週期:
自定義過濾器工廠的方式:
繼承AbstractGatewayFilterFactory
,參考源碼:org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory
。使用該方式實現的過濾器工廠的配置形式以下:
spring: cloud: gateway: routes: filters: # 過濾器工廠的名稱 - name: RequestSize # 該過濾器工廠的參數 args: maxSize: 500000
AbstractNameValueGatewayFilterFactory
,參考源碼:org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory
。使用該方式實現的過濾器工廠的配置形式以下: spring: cloud: gateway: routes: filters: # 過濾器工廠的名稱及參數以name-value的形式配置 - AddRequestHeader=S-Header, Bar
注:AbstractNameValueGatewayFilterFactory
繼承了AbstractGatewayFilterFactory
,因此實際上第二種方式是第一種方式的簡化
核心API:
exchange.getRequest().mutate().xxx
:修改requestexchange.mutate().xxx
:修改exchangechain.filter(exchange)
:傳遞給下一個過濾器處理exchange.getResponse()
:獲取響應對象注:這裏的exchange
實際類型爲ServerWebExchange
,chain
實際類型爲GatewayFilter
最後咱們來實際動手編寫一個自定義過濾器工廠,需求是記錄訪問日誌,這裏爲了簡單起見採用第二種方式實現,具體代碼以下:
package com.zj.node.gateway.filter; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; /** * 過濾器工廠必須以GatewayFilterFactory結尾, * 這是Spring Cloud Gateway的約定 * * @author 01 * @date 2019-08-15 **/ @Slf4j @Component public class PreLogGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply(NameValueConfig config) { // 使用lambda表達式來建立GatewayFilter的實例,實際就是匿名內部類的簡寫 return (exchange, chain) -> { // 經過config獲取配置的參數 log.info("配置參數:{}, {}", config.getName(), config.getValue()); // 修改request,能夠添加一些header什麼的 ServerHttpRequest modifiedRequest = exchange.getRequest() .mutate() .header("X-GatewayHeader","A","B") .build(); // 打印訪問的接口地址 String path = modifiedRequest.getURI().getPath(); log.info("訪問的接口爲:{}", path); // 修改exchange ServerWebExchange modifiedExchange = exchange.mutate() .request(modifiedRequest).build(); // 傳遞給下一個過濾器處理 return chain.filter(modifiedExchange); }; } }
最後須要添加相關配置以啓用這個過濾器工廠,以下:
spring: cloud: gateway: routes: - id: user-center uri: lb://user-center predicates: - TimeBetween=上午9:00,下午5:00 filters: # 名稱必須爲過濾器工廠類名的前綴,而且參數只能有兩個,由於NameValueConfig裏只定義了兩個屬性 - PreLog=testName,testValue
啓動項目,訪問user-center的接口,此時控制檯輸出的日誌以下:
如今咱們已經知道前面所介紹的過濾器工廠實際用於建立GatewayFilter
實例,而且這些GatewayFilter
實例僅做用於指定的路由上,那麼有沒有能夠做用於所有路由上的過濾器呢?答案是有的,這就是本小節將要介紹的全局過濾器。Spring Cloud Gateway默認就內置了許多全局過濾器,本文僅介紹如何自定義全局過濾器,關於Gateway內置的過濾器能夠參考另外一篇文章:
自定義全局過濾須要實現GlobalFilter
接口,該接口和 GatewayFilter
有同樣的方法定義,只不過 GlobalFilter
的實例會做用於全部的路由。
Tips:
官方聲明:
GlobalFilter
的接口定義以及用法在將來的版本可能會發生變化。我的判斷:
GlobalFilter
可用於生產;若是有自定義GlobalFilter
的需求,理論上也可放心使用。由於將來即便接口定義以及使用方式發生變化,理應也是平滑過渡的(好比Zuul的Fallback,原先叫ZuulFallbackProvider
,後來改叫FallbackProvider
,中間就有段時間新舊使用方式都支持,後面才逐步廢棄老的使用方式)。
接下來咱們自定義一個全局過濾器,需求是打印訪問的接口路徑以及打印該接口的訪問耗時。具體代碼以下:
package com.zj.node.gateway.filter; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 自定義全局過濾器 * * @author 01 * @date 2019-08-17 **/ @Slf4j public class MyGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); log.info("[MyGlobalFilter] 訪問的接口:{}", path); long start = System.currentTimeMillis(); return chain.filter(exchange) // then的內容會在過濾器返回的時候執行,即最後執行 .then(Mono.fromRunnable(() -> log.info("[ {} ] 接口的訪問耗時:{} /ms", path, System.currentTimeMillis() - start)) ); } }
最後須要使該全局過濾器生效,方法有不少種,能夠直接在該類上加@Component
註解,也能夠經過代碼配置(@Bean
),還有其餘的一些方式。這裏我的比較傾向於使用一個專門的配置類去實例化這些全局過濾器並交給Spring容器管理。代碼以下:
package com.zj.node.gateway.config; import com.zj.node.gateway.filter.MyGlobalFilter; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @Slf4j @Configuration public class FilterConfig { @Bean // 該註解用於指定過濾器的執行順序,數字越小越優先執行 @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter myGlobalFilter(){ log.info("create myGlobalFilter..."); return new MyGlobalFilter(); } }
啓動項目,看看咱們自定義的全局過濾器是否已生效,訪問Gateway控制檯輸出以下: