1. 程式人生 > >⑤SpringCloud 實戰:引入Zuul元件,開啟閘道器路由

⑤SpringCloud 實戰:引入Zuul元件,開啟閘道器路由

這是SpringCloud實戰系列中第4篇文章,瞭解前面第兩篇文章更有助於更好理解本文內容: [①SpringCloud 實戰:引入Eureka元件,完善服務治理](https://jinglingwang.cn/archives/eureka) [②SpringCloud 實戰:引入Feign元件,發起服務間呼叫](https://jinglingwang.cn/archives/feign) [③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡](https://jinglingwang.cn/archives/ribbon) [](https://jinglingwang.cn/archives/feign)[④SpringCloud 實戰:引入Hystrix元件,分散式系統容錯](https://jinglingwang.cn/archives/hystrix) ## 簡介 Zuul 也是 Netflix OSS 中的一員,是一個基於 JVM 路由和服務端的負載均衡器,支援動態路由、監控、彈性和安全等特性。Spring Cloud 會建立一個嵌入式 Zuul 代理來簡化一個常見用例的開發,比如使用者程式可能會對一個或多個後端服務進行呼叫,引入 Zuul 閘道器能有效避免為所有後端獨立管理CORS和身份驗證問題的需求 Zuul的使用了一系列的過濾器,這些過濾器可以完成以下功能: - 身份驗證和安全性 識別每個資源的身份驗證要求,並拒絕不滿足這些要求的請求。 - 審查與監控 跟蹤有意義的資料和統計資料,以便給我們一個準確的生產檢視。 - 動態路由 根據需要將請求動態路由到不同的後端叢集。 - 壓力測試 逐漸增加叢集的流量,以評估效能。 - 負載消減 為每種型別的請求分配容量,並丟棄超出限制的請求。 - 靜態響應處理 直接在邊緣構建一些響應,而不是將它們轉發到內部叢集Zuul 的使用 ## 實戰 Zuul ### 搭建 zuul 1. 新建一個新的專案`jlw-zuul` 2. 引入zuul依賴 ```xml org.springframework.cloud spring-cloud-starter-netflix-zuul ``` 3. 啟動類上加入註解`@EnableZuulProxy` 4. 引入Eureka註冊中心,並註冊上去 5. 現在不需要額外配置就可以啟動了,啟動之後你會看到預設的服務對映相關日誌: ```xml Mapped URL path [/eureka-provider-temp/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController] Mapped URL path [/eureka-server/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController] Mapped URL path [/eureka-provider/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController] Mapped URL path [/ribbon-client/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController] Mapped URL path [/eureka-client/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController] ``` 6. 然後就可以通過zuul閘道器來訪問後端服務了 ![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201208101829615-1036139959.png) ### 管理端點 Zuul 預設依賴了 actuator,並且會暴露`/actuator/routes`和`/actuator/filters` 兩個端點,訪問這兩個斷點,可以很直觀的檢視到路由資訊,在檢視之前需要新增以下配置: ```xml # 應該包含的端點ID,全部:* management.endpoints.web.exposure.include: * ``` 訪問[http://127.0.0.1:8000/actuator](http://127.0.0.1:8000/actuator)可以檢視所有端點資訊,訪問[http://127.0.0.1:8000/actuator/routes](http://127.0.0.1:8000/actuator/routes) 可檢視到路由資訊: ![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201208101840979-2146211634.png) ### 路由配置 **為閘道器新增字首** ```xml # 訪問閘道器的時候必須要加的路徑字首 zuul.prefix = /api ``` 新增以上配置後,訪問閘道器時路徑必須是/api/**,然後才會正確的路由到後端對應的服務 如果在轉發請求到服務的時候要去掉這個字首,可以設定strip-prefix= false來忽略 ```xml # 請求轉發前是否要刪除 zuul.prefix 設定的字首 ,true:轉發前要帶上字首(預設值),fasle:不帶上字首 zuul.routes.ecs.strip-prefix = true ``` **配置路由** ```java # 忽略註冊中心 eureka-server,*:會忽略所有的服務 zuul.ignored-services = eureka-server,eureka-client # eureka-client 服務對映規則,http://127.0.0.1:8000/ec/sayHello zuul.routes.eureka-client = /ec/** ``` 上面的配置會忽略eureka-server和eureka-client,訪問`http://127.0.0.1:8000/api/ec/**`的請求的都會被路由到eureka-client,如果沒有忽略eureka-client,則訪問`/eureka-client/**` 和`/ec/**` 都會路由到eureka-client服務。 注意`/ec/*`只會匹配一個層級,`/ec/**` 會匹配多個層級。 ![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201208102025066-219458671.png) **指定服務id和path** ```xml # 指定service-id和path zuul.routes.rcs.service-id = ribbon-client zuul.routes.rcs.path = /rc/** ``` 然後訪問[http://127.0.0.1:8000/api/rc/queryPort](http://127.0.0.1:8000/rc/queryPort)介面就會被路由到`ribbon-client`服務 **路由配置順序** 如果想按照配置的順序進行路由規則控制,則需要使用YAML,如果是使用propeties檔案,則會丟失順序。例如: ```xml zuul: routes: users: path: /myusers/** legacy: path: /** ``` 使用propeties檔案,舊的路徑可能出現在使用者路徑的前面,從而導致使用者路徑無法訪問。 **關閉重試** 可以通過將zuul.retryable設定為false來關閉Zuul的重試功能,預設值也是false。 ```java zuul.retryable=false ``` 還可以通過將zuul.routes.routename.retryable設定為false來禁用逐個路由的重試功能 ```java # 關閉指定路由的重試 zuul.routes.ecs.retryable = false ``` ### 忽略服務路由 新增以下配置會忽略指定的服務,很明顯註冊中心一般是不需要通過閘道器來訪問的,所以需要忽略它 ```java # 忽略註冊中心 eureka-server,*:會忽略所有的服務 zuul.ignored-services = eureka-server ``` 也可以通過`zuul.ignoredPatterns` 來配置你不想暴露出去的API ## 隔離策略 ### 設定訊號量 ```xml # 改為訊號量隔離 zuul.ribbon-isolation-strategy=semaphore # Hystrix的最大總訊號量 zuul.semaphore.max-semaphores=1000 # 單個路由可以使用的最大連線數 zuul.host.max-per-route-connections=500 ``` 最大總訊號量預設是100,單個路由最大的連線數預設是20,有時候併發量上不去可能就是使用的預設配置。 ### 設定獨立的執行緒池 Zuul 中預設採用訊號量隔離機制,如果想要換成執行緒,需要配置 `zuul.ribbon-isolation-strategy=THREAD`,配置後所有的路由對應的 Command 都在一個執行緒池中執行,這樣其實達不到隔離的效果,所以我們需要增加一個 zuul.thread-pool.use-separate-thread-pools 的配置,讓每個路由都使用獨立的執行緒池,zuul.thread-pool.thread-pool-key-prefix 可以為執行緒池配置對應的字首,方便除錯。 ```java ## 執行緒隔離 #zuul.ribbon-isolation-strategy=THREAD ## 每個路由使用獨立的執行緒池 #zuul.thread-pool.use-separate-thread-pools=true ## 執行緒池字首 #zuul.thread-pool.thread-pool-key-prefix=zuul-pool- ``` ## 其他配置 ### 更換Http客戶端 Zuul預設使用的是 Apache HTTP Client,需要更換的話只需要設定對應的屬性即可 ```xml # Ribbon RestClient ribbon.restclient.enabled=true # or okhttp ribbon.okhttp.enabled=true ``` ### Cookie和請求頭 Zuul 提供了一個敏感頭屬性配置,設定了該屬性後,Zuul 就不會把相關的請求頭轉發到下游的服務,比如: ```xml # 請求頭裡面的欄位不會帶到eureka-client服務 zuul.routes.ecs.sensitive-headers = jinglingwang ``` sensitiveHeaders 的預設值是Cookie、Set-Cookie、Authorization,如果把該值配置成空值,則會把所有的頭都傳遞到下游服務。 還可以通過設定zuul.sensitiveHeaders來設定全域性的敏感標頭。 如果在路由上設定了sensitiveHeaders,它將覆蓋全域性的sensitiveHeaders設定 ### 忽略請求頭 除了對路由敏感的標頭單獨設定之外,還可以設定一個名為zuul.ignoredHeaders的全域性值,比如: ```xml # 該配置的Header也不會轉發到下游服務 zuul.ignored-headers=jinglingwang ``` 在預設情況下是沒有這個配置的,如果專案中引入了Spring Security,那麼Spring Security會自動加上這個配置,預設值為: Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expries。 下游服務需要使用Spring Security的Header時,可以增加`zuul.ignoreSecurityHeaders=false`的配置 ### 檔案上傳 通過Zuul閘道器上傳檔案時,只要檔案不大,都可以正常的上傳,對於大檔案,Zuul有一個替代路徑(`/zuul/*`)可以繞過Spring DispatcherServlet,比如你的檔案服務(file-service)路由配置是`zuul.routes.file-service=/file/**`,然後你post提交檔案到`/zuul/file/**` 即可。 還有一種辦法就是直接修改可上傳檔案大小的配置: ```xml # 檔案最大值。值可以使用字尾“ MB”或“ KB”分別表示兆位元組或千位元組 spring.servlet.multipart.max-file-size=10MB # 最大請求大小 spring.servlet.multipart.max-request-size=30MB ``` 兩種辦法都需要在檔案服務裡面新增以上的配置 在上傳大檔案時也需要設定合理的超時時間: ```xml hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000 ``` ### 自定義過濾器 過濾器是 Zuul 中的核心內容,很多高階的擴充套件都需要自定義過濾器來實現,在 Zuul 中自定義一個過濾器只需要繼承 ZuulFilter,然後重寫 ZuulFilter 的四個方法即可: ```java @Component public class LogFilter extends ZuulFilter{ /** * 返回過濾器的型別,可選值有 pre、route、post、error 四種類型 * @return */ @Override public String filterType(){ return "pre"; } /** * 指定過濾器的執行順序,數字越小,優先順序越高 * 預設的filter的順序可以在FilterConstants類中檢視。 * @return */ @Override public int filterOrder(){ // pre filter return PRE_DECORATION_FILTER_ORDER - 1 ; // ROUTE filter //return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1 ; // POST filter //return SEND_RESPONSE_FILTER_ORDER - 1 ; } /** * 決定了是否執行該過濾器,true 為執行,false 為不執行 * @return */ @Override public boolean shouldFilter(){ return true; } /** * 如果shouldFilter()為true,則將呼叫此方法。該方法是ZuulFilter的核心方法 * @return 返回值會被忽略 * @throws ZuulException */ @Override public Object run() throws ZuulException{ HttpServletRequest req = (HttpServletRequest) RequestContext.getCurrentContext().getRequest(); System.out.println("ZUUL REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort() + " uri::"+ req.getRequestURI()) ; return null; } } ``` ### 禁用過濾器 Zuul 預設提供了很多過濾器(ZuulFilter),有關可啟用的過濾器列表,可以參考Zuul 過濾器的包(netflix.zuul.filters)。如果要禁用一個過濾器,可以按照`zuul...disable=true` 格式來進行設定,比如: ```xml zuul.SendResponseFilter.post.disable=true ``` ### 跨域支援 如果是外部網頁應用需要呼叫閘道器的 API,不在同一個域名下則會存在跨域的問題,想讓Zuul處理這些跨域的請求,可以通過提供自定義WebMvcConfigurer bean來完成: ```java @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { /** * 配置跨源請求處理 * @param registry */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/path/**") .allowedOrigins("https://jinglingwang.cn") .allowedMethods("GET", "POST"); } }; } ``` 上面的示例中,允許`jinglingwang.cn`的`GET`和`POST`方法將跨域請求傳送到 /path/**開頭的端點 ### Zuul 超時 有兩種情況: 1. 如果Zuul使用服務發現,則需要配置Ribbon的屬性配置超時 ```xml ribbon.ReadTimeout ribbon.SocketTimeout ``` 2. 如果通過指定URL配置了Zuul路由 ```xml # 套接字超時(以毫秒為單位)。預設為10000 zuul.host.socket-timeout-millis=15000 # 連線超時(以毫秒為單位)。預設為2000 zuul.host.connect-timeout-millis=3000 ``` ### 服務容錯與回退 Spring Cloud 中,Zuul 預設整合了 Hystrix,當Zuul中給定路由的電路跳閘時,可以通過建立FallbackProvider型別的bean提供回退響應。配置示例程式碼如下: ```xml @Component public class EurekaClientFallbackProvider implements FallbackProvider{ @Override public String getRoute(){ // 路由的server-id,* or null:為所有的路由都配置回退 return "eureka-client"; } @Override public ClientHttpResponse fallbackResponse(String route,Throwable cause){ if (cause instanceof HystrixTimeoutException) { return response(HttpStatus.GATEWAY_TIMEOUT); } else { return response(HttpStatus.INTERNAL_SERVER_ERROR); } } private ClientHttpResponse response(HttpStatus status){ return new ClientHttpResponse(){ @Override public HttpStatus getStatusCode() throws IOException{ return status; } @Override public int getRawStatusCode() throws IOException{ return status.value(); } @Override public String getStatusText() throws IOException{ return status.getReasonPhrase(); } @Override public void close(){ } @Override public InputStream getBody() throws IOException{ return new ByteArrayInputStream("eureka-client 服務暫不可用,jinglingwang請你稍後重試!".getBytes()); } @Override public HttpHeaders getHeaders(){ HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); return headers; } }; } } ``` 重啟後,執行效果如下: ![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201208102041092-674465564.png) 如果要為所有路由提供預設回退,getRoute方法返回*或null即可。 ### Ribbon client延遲載入 Zuul內部使用Ribbon來呼叫遠端URL。 預設情況下,Ribbon 客戶端在第一次呼叫時由Spring Cloud進行延遲載入。可以通過以下配置來開啟啟動時立即載入: ```java zuul.ribbon.eager-load.enabled=true ``` ### @EnableZuulProxy vs @EnableZuulServer Spring Cloud Netflix安裝了很多過濾器,具體取決於用於啟用Zuul的註解。 @EnableZuulProxy是@EnableZuulServer的超集。換句話說,@ EnableZuulProxy包含@EnableZuulServer安裝的所有過濾器。 “proxy”中的其他過濾器啟用路由功能。 如果需要一個“空白”的 Zuul,則應使用@EnableZuulSe