javascript
Spring Cloud Gateway(路由)
本篇文章主要介紹了什么是 Spring Cloud Gateway,并基于 Spring Cloud Gateway 的 Finchley.RC1 版本編寫一個 Spring Cloud Gateway 的入門案例,即基本代理的路由轉發配置。
概述
Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。
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
- 限流
- 路徑重寫
vs Netflix Zuul
Zuul 基于 Servlet 2.5(使用 3.x),使用阻塞 API,它不支持任何長連接,如 WebSockets。而 Spring Cloud Gateway 建立在 Spring Framework 5,Project Reactor 和 Spring Boot 2 之上,使用非阻塞 API,支持 WebSockets,并且由于它與 Spring 緊密集成,所以將會是一個更好的開發體驗。
要說缺點,其實 Spring Cloud Gateway 還是有的。目前它的文檔還不是很完善,官方文檔有許多還處于 TODO 狀態,網絡上關于它的文章也還比較少。如果你決定要使用它,那么你必須得有耐心通過自己閱讀源碼來解決可能遇到的問題。(2018.5.7)
坑總是會有的,就看我們怎么來填平。
術語
- 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”)執行業務邏輯。
實戰
我們之前使用 Zuul 實現了一個網關,這里我們就用 Spring Cloud Gateway 來替代它實現相同的功能。在這里我們繼續沿用之前已經寫好的服務并依次啟動:
- eureka
- producer
- consumer
新建一個標準的 Spring Boot 工程,命名為 gateway,然后在 pom.xml 中引入以下依賴坐標
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
復制application.yml 配置文件內容如下
?
配置說明:
- spring.cloud.gateway.discovery.locator.enabled:是否與服務注冊于發現組件進行結合,通過 serviceId 轉發到具體的服務實例。默認為false,設為true便開啟通過服務中心的自動根據 serviceId 創建路由的功能。
- spring.cloud.gateway.routes用于配合具體的路由規則,是一個數組。這里我創建了一個 id 為default_path_to_http的路由,其中的配置是將未匹配的請求轉發到https://windmt.com。實際上開啟了服務發現后,如果只使用默認創建的路由規則,這個 routes 不配置也是可以的,所以我就先注釋掉了不用它了。
- 網關服務監聽 10000 端口
- 指定注冊中心的地址,以便使用服務發現功能
- 調整相關包的 log 級別,以便排查問題
Spring Boot 的啟動類不用修改,直接啟動即可(少了要加@Enable***注解這步我還有點不適應),啟動后我們便可在 Eureka 中看到我們的網關服務
然后我們像之前使用 Zuul 那樣訪問?http://localhost:10000/consumer/hello/windmt
我們期待像直接訪問 consumer 那樣能返回 “Hello windmt!”,但是實際上卻出錯了,返回了 404 錯誤。我們來看一下 log
?
2018-05-07 16:20:34.643 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition CompositeDiscoveryClient_PRODUCER applying {pattern=/PRODUCER/**} to Path 2018-05-07 16:20:34.652 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition CompositeDiscoveryClient_PRODUCER applying filter {regexp=/PRODUCER/(?<remaining>.*), replacement=/${remaining}} to RewritePath 2018-05-07 16:20:34.657 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition matched: CompositeDiscoveryClient_PRODUCER 2018-05-07 16:20:34.657 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition CompositeDiscoveryClient_CONSUMER applying {pattern=/CONSUMER/**} to Path 2018-05-07 16:20:34.659 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition CompositeDiscoveryClient_CONSUMER applying filter {regexp=/CONSUMER/(?<remaining>.*), replacement=/${remaining}} to RewritePath 2018-05-07 16:20:34.660 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition matched: CompositeDiscoveryClient_CONSUMER 2018-05-07 16:20:34.662 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition CompositeDiscoveryClient_CLOUD-GATEWAY applying {pattern=/CLOUD-GATEWAY/**} to Path 2018-05-07 16:20:34.664 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition CompositeDiscoveryClient_CLOUD-GATEWAY applying filter {regexp=/CLOUD-GATEWAY/(?<remaining>.*), replacement=/${remaining}} to RewritePath 2018-05-07 16:20:34.665 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.r.RouteDefinitionRouteLocator : RouteDefinition matched: CompositeDiscoveryClient_CLOUD-GATEWAY 2018-05-07 16:20:34.732 WARN 69502 --- [ctor-http-nio-2] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [GET http://127.0.0.1:10000/consumer/hello/windmt]: Response status 404可以看到 Spring Cloud Gateway 確實為我們的 producer 和 consumer 自動創建了對應的路由,但是這里的 pattern/regexp 里都是大寫的。那我們就換成大寫的來試一下。
訪問?http://localhost:10000/CONSUMER/hello/windmt?確實返回了 “Hello, windmt!”,這時再看 log
2018-05-07 16:32:06.473 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.h.RoutePredicateHandlerMapping : Route matched: CompositeDiscoveryClient_CONSUMER 2018-05-07 16:32:06.473 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.h.RoutePredicateHandlerMapping : Mapping [Exchange: GET http://localhost:10000/CONSUMER/hello/windmt] to Route{id='CompositeDiscoveryClient_CONSUMER', uri=lb://CONSUMER, order=0, predicate=org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory$$Lambda$511/504641976@1ccb8d30, gatewayFilters=[OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory$$Lambda$513/1057677564@19e672a5, order=1}]} 2018-05-07 16:32:06.473 DEBUG 69502 --- [ctor-http-nio-2] o.s.c.g.handler.FilteringWebHandler : Sorted gatewayFilterFactories: [OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@376e7531}, order=-1}, OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory$$Lambda$513/1057677564@19e672a5, order=1}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@5782d777}, order=10000}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@75e710b}, order=10100}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@23202c31}, order=2147483637}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@b016b4e}, order=2147483646}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@26f7cdf8}, order=2147483647}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@4f824872}, order=2147483647}]
復制可以看出,Spring Cloud Gateway 自動的為我們的 consumer 創建了一個路由,類似于下邊這樣
但是這種必須將 serviceId 大寫的方式還是比較蛋疼的,雖然 Eureka 注冊中心默認顯示的都是大寫的,但是這大寫的路徑放在 URL 真的好嗎?我唯一能想到的好處就是能清晰分辨出 serviceId 和 path。
如果大寫的 URL 在瀏覽器里自動變成了小寫的,可以試試:清空緩存、使用無痕模式(command+shift+n)、在終端直接用curl。
上邊是基于服務發現的默認路由規則,如果我們要自定義路由規則怎么辦呢?
比如我們的這個服務是跟客戶服務相關的(嗯,目前它功能比較單一,只會跟客戶 say hi,但是這沒有影響),我們希望這個服務的 path 以?/customer/?開頭,具體到這個例子,就是?/costomer/hello/{name}。并且,我們還要為每個響應添加一個響應頭X-Response-Default-Foo: Default-Bar。
讓我們來修改一下配置,主要是增加一個 route,其他配置不變
routes:- id: service_customeruri: lb://CONSUMERorder: 0predicates:- Path=/customer/**filters:- StripPrefix=1- AddResponseHeader=X-Response-Default-Foo, Default-Bar新增的StripPrefix可以接受一個非負整數,對應的具體實現是StripPrefixGatewayFilterFactory,從名字就可以看出它的作用是去掉前綴的,那個整數即對應層數。具體到本例中,我們通過 Spring Cloud Gateway 訪問?/customer/hello/windmt,那么當網關服務向后轉發請求時,會去掉/customer,微服務收到的就是/hello/windmt。
現在這個 filters 下面的配置不用深究,按這么配置就好,下一篇會詳細講 Filter。
我們現在訪問?http://localhost:10000/customer/hello/windmt?可以看到能正常返回數據并且響應頭也加上了。這時候?http://localhost:10000/CONSUMER/hello/windmt?雖然依舊能正常返回數據,但是并沒有我們自定義的響應頭。
Spring Cloud Gateway 也支持通過 Java 的流式 API 進行路由的定義,如下就是一個和上邊通過配置文件配置的等效的路由,并且可以和配置文件搭配使用。
@Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {// @formatter:offreturn builder.routes().route(r -> r.path("/fluent/customer/**").filters(f -> f.stripPrefix(2).addResponseHeader("X-Response-Default-Foo", "Default-Bar")).uri("lb://CONSUMER").order(0).id("fluent_customer_service")).build();// @formatter:on }總結
本文我們簡單了解了 Spring Cloud Gateway,并用它實現了一個簡單的網關服務。既介紹了通過結合注冊中心 Eureka 來為微服務提供默認的路由,也介紹了如何通過配置文件和 API 去自定義路由,相信大家對 Spring Cloud Gateway 已經有了個初步的認識。后面的文章我們也會繼續去發現 Spring Cloud Gateway 更多強大的功能。
示例代碼可以從 Github 獲取:https://github.com/zhaoyibo/spring-cloud-study
參考
Spring Cloud Gateway
spring-cloud-gateway-sample
- 本文作者:?Yibo
- 本文鏈接:?https://windmt.com/2018/05/07/spring-cloud-13-spring-cloud-gateway-router/
- 版權聲明:?本博客所有文章除特別聲明外,均采用?CC BY-NC-SA 4.0?許可協議。轉載請注明出處!
總結
以上是生活随笔為你收集整理的Spring Cloud Gateway(路由)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: go build命令详解
- 下一篇: Spring Cloud Gateway