Springboot: 2.1.6.RELEASEhtml
SpringCloud: Greenwich.SR1前端
如無特殊說明,本系列教程全採用以上版本java
上一篇咱們主要聊到了Zuul的使用方式,以及自動轉發機制,其實Zuul還有更多的使用姿式,好比:鑑權、流量轉發、請求統計等。git
Zuul的核心是Filter,用來實現對外服務的控制。分別是「PRE」、「ROUTING」、「POST」、「ERROR」,整個生命週期能夠用下圖來表示。github
Zuul大部分功能都是經過過濾器來實現的。Zuul中定義了四種標準過濾器類型,這些過濾器類型對應於請求的典型生命週期。spring
PRE: 這種過濾器在請求被路由以前調用。咱們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。express
ROUTING: 這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。apache
OST: 這種過濾器在路由到微服務之後執行。這種過濾器可用來爲響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。後端
ERROR: 在其餘階段發生錯誤時執行該過濾器。瀏覽器
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標記處理Servlet的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記調試標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
能夠在application.yml中配置須要禁用的filter,格式:
zuul: FormBodyWrapperFilter: pre: disable: true
實現自定義Filter,須要繼承ZuulFilter的類,並覆蓋其中的4個方法。
package com.springcloud.zuulsimple.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.exception.ZuulException; /** * Created with IntelliJ IDEA. * * @User: weishiyao * @Date: 2019/7/6 * @Time: 16:10 * @email: inwsy@hotmail.com * Description: */ public class MyFilter extends ZuulFilter { @Override public String filterType() { return null; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return false; } @Override public Object run() throws ZuulException { return null; } }
咱們假設有這樣一個場景,由於服務網關應對的是外部的全部請求,爲了不產生安全隱患,咱們須要對請求作必定的限制,好比請求中含有Token便讓請求繼續往下走,若是請求不帶Token就直接返回並給出提示。
首先,將上一篇的zuul-simple copy到一個新的文件夾中,自定義一個Filter,在run()方法中驗證參數是否含有Token。
package com.springcloud.zuulsimple.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; /** * Created with IntelliJ IDEA. * * @User: weishiyao * @Date: 2019/7/6 * @Time: 16:11 * @email: inwsy@hotmail.com * Description: */ public class TokenFilter extends ZuulFilter { private final Logger logger = LoggerFactory.getLogger(TokenFilter.class); @Override public String filterType() { return "pre"; // 能夠在請求被路由以前調用 } @Override public int filterOrder() { return 0; // filter執行順序,經過數字指定 ,優先級爲0,數字越大,優先級越低 } @Override public boolean shouldFilter() { return true;// 是否執行該過濾器,此處爲true,說明須要過濾 } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString()); String token = request.getParameter("token");// 獲取請求的參數 if (StringUtils.isNotBlank(token)) { ctx.setSendZuulResponse(true); //對請求進行路由 ctx.setResponseStatusCode(200); ctx.set("isSuccess", true); return null; } else { ctx.setSendZuulResponse(false); //不對其進行路由 ctx.setResponseStatusCode(400); ctx.setResponseBody("token is empty"); ctx.set("isSuccess", false); return null; } } }
將TokenFilter加入到請求攔截隊列,在啓動類中添加如下代碼:
@Bean public TokenFilter tokenFilter() { return new TokenFilter(); }
這樣就將咱們自定義好的Filter加入到了請求攔截中。
將上一篇的Eureka和producer都CV到新的文件夾下面,依次啓動。
打開瀏覽器,咱們訪問:http://localhost:8080/spring-cloud-producer/hello?name=spring, 返回:token is empty ,請求被攔截返回。
訪問地址:http://localhost:8080/spring-cloud-producer/hello?name=spring&token=123,返回:hello spring,producer is ready,說明請求正常響應。
經過上面這例子咱們能夠看出,咱們可使用「PRE」類型的Filter作不少的驗證工做,在實際使用中咱們能夠結合shiro、oauth2.0等技術去作鑑權、驗證。
當咱們的後端服務出現異常的時候,咱們不但願將異常拋出給最外層,指望服務能夠自動進行降級處理。Zuul給咱們提供了這樣的支持。當某個服務出現異常時,直接返回咱們預設的信息。
咱們經過自定義的fallback方法,而且將其指定給某個route來實現該route訪問出問題的熔斷處理。主要繼承FallbackProvider接口來實現,FallbackProvider默認有兩個方法,一個用來指明熔斷攔截哪一個服務,一個定製返回內容。
/* * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.netflix.zuul.filters.route; import org.springframework.http.client.ClientHttpResponse; /** * Provides fallback when a failure occurs on a route. * * @author Ryan Baxter * @author Dominik Mostek */ public interface FallbackProvider { /** * The route this fallback will be used for. * @return The route the fallback will be used for. */ String getRoute(); /** * Provides a fallback response based on the cause of the failed execution. * @param route The route the fallback is for * @param cause cause of the main method failure, may be <code>null</code> * @return the fallback response */ ClientHttpResponse fallbackResponse(String route, Throwable cause); }
實現類經過實現getRoute方法,告訴Zuul它是負責哪一個route定義的熔斷。而fallbackResponse方法則是告訴 Zuul 斷路出現時,它會提供一個什麼返回值來處理請求。
咱們以上面的spring-cloud-producer服務爲例,定製它的熔斷返回內容。
package com.springcloud.zuulsimple.component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * Created with IntelliJ IDEA. * * @User: weishiyao * @Date: 2019/7/6 * @Time: 16:25 * @email: inwsy@hotmail.com * Description: */ @Component public class ProducerFallback implements FallbackProvider { private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class); //指定要處理的 service。 @Override public String getRoute() { return "spring-cloud-producer"; } public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("The service is unavailable.".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { if (cause != null && cause.getCause() != null) { String reason = cause.getCause().getMessage(); logger.info("Excption {}",reason); } return fallbackResponse(); } }
當服務出現異常時,打印相關異常信息,並返回」The service is unavailable.」。
須要注意點,這裏咱們須要將Eureka的配置文件修改一下:
server: port: 8761 spring: application: name: eureka-serve eureka: # server: # enable-self-preservation: false client: register-with-eureka: false service-url: defaultZone: http://localhost:8761/eureka/
將Eureka的自我保護模式打開,若是這裏不開啓自我保護模式,producer一中止服務,這個服務直接在Eureka下線,Zuul會直接報錯找不到對應的producer服務。
咱們順次啓動這三個服務。
如今打開瀏覽器,訪問連接:http://localhost:8080/spring-cloud-producer/hello?name=spring&token=123, 能夠看到頁面正常返回:hello spring,producer is ready,如今咱們把producer這個服務停下,再刷新下頁面,能夠看到頁面返回:The service is unavailable.。這樣咱們熔斷也測試成功。
咱們實際使用Zuul的方式如上圖,不一樣的客戶端使用不一樣的負載將請求分發到後端的Zuul,Zuul在經過Eureka調用後端服務,最後對外輸出。所以爲了保證Zuul的高可用性,前端能夠同時啓動多個Zuul實例進行負載,在Zuul的前端使用Nginx或者F5進行負載轉發以達到高可用性。
參考: http://www.ityouknow.com/springcloud/2018/01/20/spring-cloud-zuul.html