springcloud之服務網關Zuul高級篇

上篇文章主要介紹了Zuul網關使用模式,以及自動轉發機制,但其實Zuul還有更多的應用場景,比如:鑑權流量轉發請求統計等等,這些功能都可以使用Zuul來實現。

Zuul的核心

Filter是Zuul的核心,用來實現對外服務的控制Filter的生命週期有4個,分別是“PRE”、“ROUTING”、“POST”、“ERROR”,整個生命週期可以用下圖來表示。

 

 Zuul大部分功能都是通過過濾器來實現的,這些過濾器類型對應於請求的典型生命週期。

  • PRE: 這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。
  • ROUTING這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。
  • POST這種過濾器在路由到微服務以後執行這種過濾器可用來爲響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
  • ERROR在其他階段發生錯誤時執行該過濾器。 除了默認的過濾器類型,Zuul還允許我們創建自定義的過濾器類型。例如,我們可以定製一種STATIC類型的過濾器,直接在Zuul中生成響應,而不將請求轉發到後端的微服務。

Zuul中默認實現的Filter

類型順序過濾器功能
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 處理正常的請求響應

禁用指定的Filter

可以在application.yml中配置需要禁用的filter,格式:

zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

自定義Filter

實現自定義Filter,需要繼承ZuulFilter的類,並覆蓋其中的4個方法。

public class MyFilter extends ZuulFilter {
    @Override
    String filterType() {
        return "pre"; //定義filter的類型,有pre、route、post、error四種
    }

    @Override
    int filterOrder() {
        return 10; //定義filter的順序,數字越小表示順序越高,越先執行
    }

    @Override
    boolean shouldFilter() {
        return true; //表示是否需要執行該filter,true表示執行,false表示不執行
    }

    @Override
    Object run() {
        return null; //filter需要執行的具體操作
    }
}

自定義Filter示例

我們假設有這樣一個場景,因爲服務網關應對的是外部的所有請求,爲了避免產生安全隱患,我們需要對請求做一定的限制比如請求中含有Token便讓請求繼續往下走,如果請求不帶Token就直接返回並給出提示。

首先自定義一個Filter在run()方法中驗證參數是否含有Token。

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() {
        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加入到了請求攔截中。

測試

我們依次啓動示例項目:spring-cloud-eurekaspring-cloud-producerspring-cloud-zuul

這個三個項目均爲上一篇示例項目,spring-cloud-zuul稍微進行改造。

訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo,返回:token is empty ,請求被攔截返回。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,返回:hello neo,this is first messge,說明請求正常響應。

通過上面這例子我們可以看出,我們可以使用“PRE”類型的Filter做很多的驗證工作,在實際使用中我們可以結合shiro、oauth2.0等技術去做鑑權、驗證。

路由熔斷

當我們的後端服務出現異常的時候,我們不希望將異常拋出給最外層,期望服務可以自動進行一降級Zuul給我們提供了這樣的支持。當某個服務出現異常時,直接返回我們預設的信息

我們通過自定義的fallback方法,並且將其指定給某個route來實現該route訪問出問題的熔斷處理

主要繼承ZuulFallbackProvider接口來實現,ZuulFallbackProvider默認有兩個方法,一個用來指明熔斷攔截哪個服務,一個定製返回內容。

public interface ZuulFallbackProvider {
   /**
     * The route this fallback will be used for.
     * @return The route the fallback will be used for.
     */
    public String getRoute();

    /**
     * Provides a fallback response.
     * @return The fallback response.
     */
    public ClientHttpResponse fallbackResponse();
}

實現類通過實現getRoute方法,告訴Zuul它是負責哪個route定義的熔斷。fallbackResponse方法則是告訴 Zuul 斷路出現時,它會提供一個什麼返回值來處理請求。

後來Spring又擴展了此類,豐富了返回方式,在返回的內容中添加了異常信息,因此最新版本建議直接繼承類FallbackProvider 。

我們以上面的spring-cloud-producer服務爲例,定製它的熔斷返回內容

@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(Throwable cause) {
        if (cause != null && cause.getCause() != null) {
            String reason = cause.getCause().getMessage();
            logger.info("Excption {}",reason);
        }
        return fallbackResponse();
    }
}

當服務出現異常時,打印相關異常信息,並返回”The service is unavailable.”。

啓動項目spring-cloud-producer-2,這時候服務中心會有兩個spring-cloud-producer項目,我們重啓Zuul項目。

再手動關閉spring-cloud-producer-2項目,多次訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,會交替返回:

hello neo,this is first messge
The service is unavailable.
...

根據返回結果可以看出:spring-cloud-producer-2項目已經啓用了熔斷,返回:The service is unavailable.

Zuul 目前只支持服務級別的熔斷,不支持具體到某個URL進行熔斷。

路由重試

有時候因爲網絡或者其它原因,服務可能會暫時的不可用,這個時候我們希望可以再次對服務進行重試Zuul也幫我們實現了此功能,需要結合Spring Retry 一起來實現。下面我們以上面的項目爲例做演示。

添加Spring Retry依賴

首先在spring-cloud-zuul項目中添加Spring Retry依賴。

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

開啓Zuul Retry

再配置文件中配置啓用Zuul Retry

#是否開啓重試功能
zuul.retryable=true
#對當前服務的重試次數
ribbon.MaxAutoRetries=2
#切換相同Server的次數
ribbon.MaxAutoRetriesNextServer=0

這樣我們就開啓了Zuul的重試功能。

測試

我們對spring-cloud-producer-2進行改造,在hello方法中添加定時,並且在請求的一開始打印參數。

@RequestMapping("/hello")
public String index(@RequestParam String name) {
    logger.info("request two name is "+name);
    try{
        Thread.sleep(1000000);
    }catch ( Exception e){
        logger.error(" hello two error",e);
    }
    return "hello "+name+",this is two messge";
}

重啓 spring-cloud-producer-2和spring-cloud-zuul項目。

訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,當頁面返回:The service is unavailable.時查看項目spring-cloud-producer-2後臺日誌如下:

2018-01-22 19:50:32.401  INFO 19488 --- [io-9001-exec-14] o.s.c.n.z.f.route.FallbackProvider       : request two name is neo
2018-01-22 19:50:33.402  INFO 19488 --- [io-9001-exec-15] o.s.c.n.z.f.route.FallbackProvider       : request two name is neo
2018-01-22 19:50:34.404  INFO 19488 --- [io-9001-exec-16] o.s.c.n.z.f.route.FallbackProvider       : request two name is neo

說明進行了三次的請求,也就是進行了兩次的重試。

這樣也就驗證了我們的配置信息,完成了Zuul的重試功能。

注意

開啓重試在某些情況下是有問題的,比如當壓力過大,一個實例停止響應時,路由將流量轉到另一個實例,很有可能導致最終所有的實例全被壓垮。說到底,斷路器的其中一個作用就是防止故障或者壓力擴散。用了retry,斷路器就只有在該服務的所有實例都無法運作的情況下才能起作用。這種時候,斷路器的形式更像是提供一種友好的錯誤信息,或者假裝服務正常運行的假象給使用者

不用retry,僅使用負載均衡和熔斷,就必須考慮到是否能夠接受單個服務實例關閉和eureka刷新服務列表之間帶來的短時間的熔斷。如果可以接受,就無需使用retry。

Zuul高可用

 

 我們實際使用Zuul的方式如上圖,不同的客戶端使用不同的負載將請求分發到後端的Zuul,Zuul在通過Eureka調用後端服務,最後對外輸出。

因此爲了保證Zuul的高可用性,前端可以同時啓動多個Zuul實例進行負載,在Zuul的前端使用Nginx或者F5進行負載轉發以達到高可用性。

參考網址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章