[菜鳥SpringCloud實戰(zhàn)入門]第九章:服務網關Zuul體驗

前言

歡迎來到菜鳥SpringCloud實戰(zhàn)入門系列(SpringCloudForNoob),該系列通過層層遞進的實戰(zhàn)視角,來一步步學習和理解SpringCloud。

本系列適合有一定Java以及SpringBoot基礎的同學閱讀。

每篇文章末尾都附有本文對應的Github源代碼,方便同學調試。

實戰(zhàn)版本

  • SpringBoot:2.0.3.RELEASE
  • SpringCloud:Finchley.RELEASE

-----正文開始-----

[菜鳥SpringCloud實戰(zhàn)入門]第九章:服務網關Zuul體驗

服務網關 Zuul

Zuul介紹

外部的應用如何來訪問內部各種各樣的微服務呢?在微服務架構中,后端服務往往不直接開放給調用端,而是通過一個API網關根據請求的url,路由到相應的服務。當添加API網關后,在第三方調用端和服務提供方之間就創(chuàng)建了一面墻,這面墻直接與調用方通信進行權限控制,后將請求均衡分發(fā)給后臺服務端。

這個API網關便是Spring Cloud Zuul

Zuul提供動態(tài)路由,監(jiān)控,彈性,安全等的邊緣服務。Zuul是Netflix出品的一個基于JVM路由和服務端的負載均衡器。

Zuul與Ribbon以及Eureka配合:

Zuul、Ribbon以及Eureka結合可以實現(xiàn)智能路由和負載均衡的功能;網關將所有服務的API接口統(tǒng)一聚合,統(tǒng)一對外暴露。外界調用API接口時,不需要知道微服務系統(tǒng)中各服務相互調用的復雜性,保護了內部微服務單元的API接口;網關可以做用戶身份認證和權限認證,防止非法請求操作API接口;網關可以實現(xiàn)監(jiān)控功能,實時日志輸出,對請求進行記錄;網關可以實現(xiàn)流量監(jiān)控,在高流量的情況下,對服務降級;API接口從內部服務分離出來,方便做測試。

Zuul通過Servlet來實現(xiàn),通過自定義的ZuulServlet來對請求進行控制。核心是一系列過濾器,可以在Http請求的發(fā)起和響應返回期間執(zhí)行一系列過濾器。Zuul采取了動態(tài)讀取、編譯和運行這些過濾器。過濾器之間不能直接通信,而是通過RequestContext對象來共享數(shù)據,每個請求都會創(chuàng)建一個RequestContext對象。

Zuul生命周期如下圖。

image

Zuul大部分功能都是通過過濾器來實現(xiàn)的,這些過濾器類型對應于請求的典型生命周期。

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

Zuul中默認實現(xiàn)的Filter:

圖片來自:

http://www.ityouknow.com/springcloud/2018/01/20/spring-cloud-zuul.html

image

在yml中可以禁用指定的Filter:

zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

新建Zuul子模塊

本章需要用到之前章節(jié)的的模塊:

  • eureka(eureka):第一章
  • eureka-hi(服務提供者):第二章

我們新建子模塊(新建模塊如有疑惑請看教程第一章),名為zuul

修改pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zuul</name>
    <packaging>jar</packaging>
    <description>Demo project for Spring Boot</description>

    <!--父工程的依賴-->
    <parent>
        <groupId>com.pricemonitor</groupId>
        <artifactId>springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>


    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

</project>

在主類中加上@EnableZuulProxy:

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }

}

修改yml文件:

  • 設置端口為8773
  • 在eureka注冊
  • 設置個producer路由,把/producer/映射到service-hi的服務
spring:
  application:
    name: gateway-service-zuul
server:
  port: 8773
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
zuul:
  routes:
    hello:
      path: /producer/**
      serviceId: service-hi

單機zuul運行

先后運行eureka,eureka-hi和zuul

image.png

使用官網來訪問8763上eureka-hi提供的服務:

http://localhost:8773/producer/hi/rude3knife

得到:

image

說明網關正確轉發(fā)了請求

zuul服務集群模擬

為了更好的模擬服務集群,我們再新開一個eureka-hi,端口號為8764,只需要修改yml的配置文件,然后重新運行即可。

image

你會看到來自8763和8764隨機提供的服務:

image

zuul默認路由規(guī)則

Zuul其實已經代理所有注冊到Eureka Server的微服務,并且Zuul的路由規(guī)則如下:http://ZUUL_HOST:ZUUL_PORT/微服務在Eureka上的serviceId/**會被轉發(fā)到serviceId對應的微服務。

我們試驗一下, http://localhost:8773/service-hi/hi/rude3knife

image

zuul自定義filter

最上面的簡介中我們提到,Zuul中有默認實現(xiàn)的Filter,當然也可以實現(xiàn)自己的自定義Filter,用來完成一些鑒權、流量轉發(fā)、請求統(tǒng)計等工作。我們來實現(xiàn)一個自定義filter。

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

新建TokenFilter類:

package com.pricemonitor.zuul;

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;

public class TokenFilter extends ZuulFilter {

    private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);

    @Override
    public String filterType() {
        // 可以在請求被路由之前調用
        return "pre";
    }

    @Override
    public int filterOrder() {
        // filter執(zhí)行順序,通過數(shù)字指定 ,優(yōu)先級為0,數(shù)字越大,優(yōu)先級越低
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        // 是否執(zhí)行該過濾器,此處為true,說明需要過濾
        return 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;
        }
    }
}

隨后,在啟動類注入這個bean

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }

    @Bean
    public TokenFilter tokenFilter() {
        return new TokenFilter();
    }
}

運行eureka注冊中心,eureka-hi服務提供者,zuul網關三個子模塊。

不帶token訪問:

http://localhost:8773/producer/hi/rude3knife

image

帶著token訪問:

http://localhost:8773/producer/hi/rude3knife?token=dsa

image

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

網關路由熔斷

之前我們學過hystrix的熔斷,主要針對的是內部服務互相調用的熔斷,而zuul的熔斷則主要服務于外部接口調用。

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

實現(xiàn):主要是通過繼承FallbackProvider(之前繼承的是ZuulFallbackProvider:默認有兩個方法,一個用來指明熔斷攔截哪個服務,一個定制返回內容。)

網關路由重試

可以配合Spring Retry,來對暫時不可用的服務進行重試。

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

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

本章代碼

https://github.com/qqxx6661/springcloud_for_noob/tree/master/09-spring-cloud-zuul

參考

http://www.ityouknow.com/springcloud/2017/06/01/gateway-service-zuul.html

http://www.ityouknow.com/springcloud/2018/01/20/spring-cloud-zuul.html

https://www.cnblogs.com/cralor/p/9234697.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容