SpringCloud組件之Zuul

Zuul是Netflix開源的微服務(wù)網(wǎng)關(guān),可以和Eureka、Ribbon、Hystrix等組件配合使用,Spring Cloud對Zuul進行了整合與增強,Zuul默認使用的HTTP客戶端是Apache HTTPClient,也可以使用RestClient或okhttp3.OkHttpClient。 Zuul的主要功能是路由轉(zhuǎn)發(fā)和過濾器。路由功能是微服務(wù)的一部分,比如/demo/test轉(zhuǎn)發(fā)到到demo服務(wù)。zuul默認和Ribbon結(jié)合實現(xiàn)了負載均衡的功能

本文介紹zuul的工作原理和如何搭建zuul服務(wù)以及介紹相關(guān)知識點

一、工作原理

zuul的核心是一系列的filters, 其作用類比Servlet框架的Filter,或者AOP。zuul把請求路由到用戶處理邏輯的過程中,這些filter參與一些過濾處理,比如Authentication,Load Shedding等

zuul.png

Zuul使用一系列不同類型的過濾器,使我們能夠快速靈活地將功能應(yīng)用于我們的邊緣服務(wù)。這些過濾器可幫助我們執(zhí)行以下功能

  • 身份驗證和安全性 - 確定每個資源的身份驗證要求并拒絕不滿足這些要求的請求
  • 洞察和監(jiān)控 - 在邊緣跟蹤有意義的數(shù)據(jù)和統(tǒng)計數(shù)據(jù),以便為我們提供準確的生產(chǎn)視圖
  • 動態(tài)路由 - 根據(jù)需要動態(tài)地將請求路由到不同的后端群集
  • 壓力測試 - 逐漸增加群集的流量以衡量性能。
  • Load Shedding - 為每種類型的請求分配容量并刪除超過限制的請求
  • 靜態(tài)響應(yīng)處理 - 直接在邊緣構(gòu)建一些響應(yīng),而不是將它們轉(zhuǎn)發(fā)到內(nèi)部集群

過濾器的生命周期

time.png

二、zuul組件

  • zuul-core--zuul核心庫,包含編譯和執(zhí)行過濾器的核心功能。
  • zuul-simple-webapp--zuul Web應(yīng)用程序示例,展示了如何使用zuul-core構(gòu)建應(yīng)用程序。
  • zuul-netflix--lib包,將其他NetflixOSS組件添加到Zuul中,例如使用功能區(qū)進去路由請求處理。
  • zuul-netflix-webapp--webapp,它將zuul-core和zuul-netflix封裝成一個簡易的webapp工程包。

三、搭建一個注冊Eureka中心的Web服務(wù)

1、導(dǎo)入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、啟動類

/**
 * @author Gjing
 */
@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3、配置文件

server:
  port: 8090
spring:
  application:
    name: demo
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

4、提供接口外部訪問

/**
 * @author Gjing
 **/
@RestController
public class TestController {

    @GetMapping("/test")
    public ResponseEntity test() {
        return ResponseEntity.ok("ok");
    }
}

四、搭建Zuul服務(wù)

這里不講解Eureka服務(wù)搭建,不了解Eureka的可以查看這篇文章:SpringCloud組件之Eureka

1、導(dǎo)入zuul和eureka依賴

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

2、啟動類標注注解

/**
 * @author Gjing
 */
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

3、配置文件

a、使用Eureka負載路由方式
server:
  port: 8080
spring:
  application:
    name: zuul
# 配置Eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
# 構(gòu)建路由地址
zuul:
  routes:
    # 這里可以自定義
    demo2:
      # 匹配的路由規(guī)則
      path: /demo/**
      # 路由的目標服務(wù)名
      serviceId: demo

b、不使用eureka負載方式路由,采取請求地址路由

server:
  port: 8080
spring:
  application:
    name: zuul
# 配置eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
# 構(gòu)建路由地址
zuul:
  routes:
    # 這里可以自定義
    demo2:
      # 匹配的路由規(guī)則
      path: /demo/**
      # 路由的目標服務(wù)名
      url: demo
# 關(guān)閉使用eureka負載路由
ribbon:
  eureka:
    enabled: false
# 如果不使用eureka的話,需要自己定義路由的那個服務(wù)的其他負載服務(wù)
demo:
  ribbon:
    # 這里寫你要路由的demo服務(wù)的所有負載服務(wù)請求地址,本項目只啟動一個,因此只寫一個
    listOfServers: http://localhost:8090/
c、如果不想依賴于Eureka使用zuul,那么可使用以下配置方式
server:
  port: 8080
spring:
  application:
    name: zuul
# 構(gòu)建路由地址
zuul:
  routes:
    # 這里可以自定義
    demo2:
      # 匹配的路由規(guī)則
      path: /demo/**
      # 路由的目標地址
      url: http://localhost:8090/

4、啟動項目并訪問即可

http://localhost:8080/demo/test

五、使用Zuul過濾器

為了讓api網(wǎng)關(guān)組件可以被更方便的使用,它在http請求生命周期的各個階段默認實現(xiàn)了一批核心過濾器,它們會在api網(wǎng)關(guān)服務(wù)啟動的時候被自動加載和啟動。我們可以在源碼中查看和了解它們,它們定義與spring-cloud-netflix-core模塊的org.springframework.cloud.netflix.zuul.filters包下。在默認啟動的過濾器中包含三種不同生命周期的過濾器,這些過濾器都非常重要,可以幫組我們理解zuul對外部請求處理的過程,以及幫助我們在此基礎(chǔ)上擴展過濾器去完成自身系統(tǒng)需要的功能

1、pre過濾器

  • ServletDetectionFilter

ServletDetectionFilter:它的執(zhí)行順序為-3,是最先被執(zhí)行的過濾器。該過濾器總是會被執(zhí)行,主要用來檢測當前請求是通過Spring的DispatcherServlet處理運行的,還是通過ZuulServlet來處理運行的。它的檢測結(jié)果會以布爾類型保存在當前請求上下文的isDispatcherServletRequest參數(shù)中,這樣后續(xù)的過濾器中,我們就可以通過RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法來判斷請求處理的源頭,以實現(xiàn)后續(xù)不同的處理機制。一般情況下,發(fā)送到api網(wǎng)關(guān)的外部請求都會被Spring的DispatcherServlet處理,除了通過/zuul/路徑訪問的請求會繞過DispatcherServlet(比如之前我們說的大文件上傳),被ZuulServlet處理,主要用來應(yīng)對大文件上傳的情況。另外,對于ZuulServlet的訪問路徑/zuul/,我們可以通過zuul.servletPath參數(shù)進行修改。

  • Servlet30WrapperFilter

它的執(zhí)行順序為-2,是第二個執(zhí)行的過濾器,目前的實現(xiàn)會對所有請求生效,主要為了將原始的HttpServletRequest包裝成Servlet30RequestWrapper對象。

  • FormBodyWrapperFilter

它的執(zhí)行順序為-1,是第三個執(zhí)行的過濾器。該過濾器僅對兩類請求生效,第一類是Context-Type為application/x-www-form-urlencoded的請求,第二類是Context-Type為multipart/form-data并且是由String的DispatcherServlet處理的請求(用到了ServletDetectionFilter的處理結(jié)果)。而該過濾器的主要目的是將符合要求的請求體包裝成FormBodyRequestWrapper對象

  • DebugFilter

它的執(zhí)行順序為1,是第四個執(zhí)行的過濾器,該過濾器會根據(jù)配置參數(shù)zuul.debug.request和請求中的debug參數(shù)來決定是否執(zhí)行過濾器中的操作。而它的具體操作內(nèi)容是將當前請求上下文中的debugRouting和debugRequest參數(shù)設(shè)置為true。由于在同一個請求的不同生命周期都可以訪問到這二個值,所以我們在后續(xù)的各個過濾器中可以利用這二個值來定義一些debug信息,這樣當線上環(huán)境出現(xiàn)問題的時候,可以通過參數(shù)的方式來激活這些debug信息以幫助分析問題,另外,對于請求參數(shù)中的debug參數(shù),我們可以通過zuul.debug.parameter來進行自定義

  • PreDecorationFilter

執(zhí)行順序是5,是pre階段最后被執(zhí)行的過濾器,該過濾器會判斷當前請求上下文中是否存在forward.do和serviceId參數(shù),如果都不存在,那么它就會執(zhí)行具體過濾器的操作(如果有一個存在的話,說明當前請求已經(jīng)被處理過了,因為這二個信息就是根據(jù)當前請求的路由信息加載進來的)。而當它的具體操作內(nèi)容就是為當前請求做一些預(yù)處理,比如說,進行路由規(guī)則的匹配,在請求上下文中設(shè)置該請求的基本信息以及將路由匹配結(jié)果等一些設(shè)置信息等,這些信息將是后續(xù)過濾器進行處理的重要依據(jù),我們可以通過RequestContext.getCurrentContext()來訪問這些信息。另外,我們還可以在該實現(xiàn)中找到對HTTP頭請求進行處理的邏輯,其中包含了一些耳熟能詳?shù)念^域,比如X-Forwarded-Host,X-Forwarded-Port。另外,對于這些頭域是通過zuul.addProxyHeaders參數(shù)進行控制的,而這個參數(shù)默認值是true,所以zuul在請求跳轉(zhuǎn)時默認會為請求增加X-Forwarded-*頭域,包括X-Forwarded-Host,X-Forwarded-Port,X-Forwarded-For,X-Forwarded-Prefix,X-Forwarded-Proto。也可以通過設(shè)置zuul.addProxyHeaders=false關(guān)閉對這些頭域的添加動作

2、route過濾器

  • RibbonRoutingFilter

它的執(zhí)行順序為10,是route階段的第一個執(zhí)行的過濾器。該過濾器只對請求上下文中存在serviceId參數(shù)的請求進行處理,即只對通過serviceId配置路由規(guī)則的請求生效。而該過濾器的執(zhí)行邏輯就是面向服務(wù)路由的核心,它通過使用ribbon和hystrix來向服務(wù)實例發(fā)起請求,并將服務(wù)實例的請求結(jié)果返回

  • SimpleHostRoutingFilter

它的執(zhí)行順序為100,是route階段的第二個執(zhí)行的過濾器。該過濾器只對請求上下文存在routeHost參數(shù)的請求進行處理,即只對通過url配置路由規(guī)則的請求生效。而該過濾器的執(zhí)行邏輯就是直接向routeHost參數(shù)的物理地址發(fā)起請求,從源碼中我們可以知道該請求是直接通過httpclient包實現(xiàn)的,而沒有使用Hystrix命令進行包裝,所以這類請求并沒有線程隔離和斷路器的保護

  • SendForwardFilter

它的執(zhí)行順序是500,是route階段第三個執(zhí)行的過濾器。該過濾器只對請求上下文中存在的forward.do參數(shù)進行處理請求,即用來處理路由規(guī)則中的forward本地跳轉(zhuǎn)裝配

3、post過濾器

  • SendErrorFilter

它的執(zhí)行順序是0,是post階段的第一個執(zhí)行的過濾器。該過濾器僅在請求上下文中包含error.status_code參數(shù)(由之前執(zhí)行的過濾器設(shè)置的錯誤編碼)并且還沒有被該過濾器處理過的時候執(zhí)行。而該過濾器的具體邏輯就是利用上下文中的錯誤信息來組成一個forward到api網(wǎng)關(guān)/error錯誤端點的請求來產(chǎn)生錯誤響應(yīng)

  • SendResponseFilter

它的執(zhí)行順序為1000,是post階段最后執(zhí)行的過濾器,該過濾器會檢查請求上下文中是否包含請求響應(yīng)相關(guān)的頭信息,響應(yīng)數(shù)據(jù)流或是響應(yīng)體,只有在包含它們其中一個的時候執(zhí)行處理邏輯。而該過濾器的處理邏輯就是利用上下文的響應(yīng)信息來組織需要發(fā)送回客戶端的響應(yīng)內(nèi)容

使用案例

如果前端發(fā)起請求沒有帶指定請求頭將不進允許請求,如果需要讀取cookie等敏感信息,要在配置文件中加入sensitive-headers:,下面有對該配置的詳解

/**
 * @author Gjing
 **/
@Component
public class GlobalFilter extends ZuulFilter {
    @Override
    public String filterType() {
        //設(shè)置過濾類型
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //設(shè)置過過濾器優(yōu)先級
        return -4;
    }

    @Override
    public boolean shouldFilter() {
        //是否需要過濾
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            //返回錯誤信息
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            context.setResponseBody(HttpStatus.UNAUTHORIZED.getReasonPhrase());
            context.setSendZuulResponse(false);
            return null;
        }
        return null;
    }
}

項目啟動后如果訪問不帶Token請求頭,將被攔截,返回Unauthorized

Zuul相關(guān)知識點

1、路由配置

zuul通過與eureka的整合,實現(xiàn)了對服務(wù)實例的自動化維護,所以使用服務(wù)路由配置的時候,不需要向傳統(tǒng)路由配置方式那樣為serviceId指定具體服務(wù)實例地址,只需要通過zuul.routes.<route>.pathzuul.routes.<route>.serviceId參數(shù)對的方式進行配置即可

zuul:
  routes:
    # 這里可以自定義
    demo2:
      # 匹配的路由規(guī)則
      path: /demo/**
      # 路由的目標服務(wù)名
      serviceId: demo

除了path和serviceId鍵值對的配置方式之外,還有一種簡單的配置:zuul.routes.<serviceId>=<path>,其中<serviceId>用來指定路由的具體服務(wù)名,<path>用來配置匹配的請求表達式

zuul:
  routes:
    demo: /demo/**

2、路徑匹配

在zuul中,路由匹配的路徑表達式采用ant風(fēng)格定義

通配符 說明
? 匹配任意單個字符
* 匹配任意數(shù)量的字符
** 匹配任意數(shù)量的字符,支持多級目錄

3、忽略表達式

通過path參數(shù)定義的ant表達式已經(jīng)能夠完成api網(wǎng)關(guān)上的路由規(guī)則配置功能,但是為了更細粒度和更為靈活地配置理由規(guī)則,zuul還提供了一個忽略表達式參數(shù)zuul.ignored-patterns。該參數(shù)可以用來設(shè)置不希望被api網(wǎng)關(guān)進行路由的url表達式

zuul:
  routes:
    demo:
      path: /demo/**
      serviceId: demo
  # 不路由demo2開頭的任意請求
  ignored-patterns: /demo2/**

4、路由前綴

為了方便地為路由規(guī)則增加前綴信息,zuul提供了zuul.prefix參數(shù)來進行設(shè)置。比如,希望為網(wǎng)關(guān)上的路由規(guī)則增加/api前綴,那么我們可以在配置文件中增加配置:zuul.prefix=/api。另外,對于代理前綴會默認從路徑中移除,我們可以通過設(shè)置zuul.strip-prefix=false(默認為true,默認為true時前綴生效,比如http://localhost:8080/api/demo/test)來關(guān)閉該移除代理前綴的動作

5、本地跳轉(zhuǎn)

在zuul實現(xiàn)的api網(wǎng)關(guān)路由功能中,還支持forward形式的服務(wù)端跳轉(zhuǎn)配置。實現(xiàn)方式非常簡單,只需要通過使用path與url的配置方式就能完成,通過url中使用forward來指定需要跳轉(zhuǎn)的服務(wù)器資源路徑。

a、在zuul服務(wù)中添加一個接口
/**
 * @author Gjing
 **/
@RestController
public class HelloController {

    @GetMapping("/test/hello")
    public String test() {
        return "hello zuul";
    }
}
b、配置文件
zuul:
  routes:
    zuul-service:
      path: /api/**
      serviceId: forward:/test/

啟動后訪問http://localhost:8080/api/hello即可

6、cookie與頭信息

默認情況下,spring cloud zuul在請求路由時,會過濾掉http請求頭信息中一些敏感信息,防止它們被傳遞到下游的外部服務(wù)器。默認的敏感頭信息通過zuul.sensitiveHeaders參數(shù)定義,默認包括cookie,set-Cookie,authorization三個屬性。所以,我們在開發(fā)web項目時常用的cookie在spring cloud zuul網(wǎng)關(guān)中默認時不傳遞的,這就會引發(fā)一個常見的問題,如果我們要將使用了spring security,shiro等安全框架構(gòu)建的web應(yīng)用通過spring cloud zuul構(gòu)建的網(wǎng)關(guān)來進行路由時,由于cookie信息無法傳遞,我們的web應(yīng)用將無法實現(xiàn)登錄和鑒權(quán)。為了解決這個問題,以下介紹兩種配置方式

  • 通過設(shè)置全局參數(shù)為空來覆蓋默認值
zuul:
  routes:
    demo:
      path: /demo/**
      serviceId: demo
  # 允許敏感頭,設(shè)置為空就行了
  sensitive-headers:
  • 通過指定路由的參數(shù)來設(shè)置
zuul:
  routes:
    demo:
      path: /demo/**
      serviceId: demo
      # 將指定路由的敏感頭設(shè)置為空
      sensitiveHeaders:

文章就介紹到這里啦,如果各位讀者發(fā)現(xiàn)哪里有誤,給我評論留言哦,demo源代碼地址:SpringCloud-demo

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容