從零學SpringCloud系列(七):API網關Zuul

一、爲什麼需要API網關

通過前面對幾個組件的 介紹,我們基本可以構建一個下圖中的簡單的微服務架構系統:

        

我們聚焦到Open Service 和 外部調用的地方,隨着下方服務的增多,我們需要手動維護負載均衡器中的服務列表,並且如果我們需要服務的微服務接口都需要 權限校驗,這樣我們需要在每個對外服務中維護一套這樣的邏輯,這樣會加重開發和測試人員的負擔。

爲了解決上面問題,API 網關的概念應運而生。API網關是一個更爲智能的服務器,它的定義類似於面向對象中的Facade模式,它的存在就像是整個微服務 架構系統的門面一樣,所有的外部客戶端訪問都需要經過它來進行調度和過濾。它除了要實現請求路由、負載均衡、校驗過濾等功能之外,還需要更多能力,比如與服務 治理框架的結合,請求轉發時的熔斷,服務的聚合等一系列高級功能。

 二、Zuul的作用

  • 統一入口:未全部爲服務提供一個唯一的入口,網關起到外部和內部隔離的作用,保障了後臺服務的安全性。
  • 鑑權校驗:識別每個請求的權限,拒絕不符合要求的請求。
  • 動態路由:動態的將請求路由到不同的後端集羣中。
  • 減少客戶端與服務端的耦合:服務可以獨立發展,通過網關層來做映射。

三、Zuul工作原理

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

                  

zuul使用 一系列不同類型的過濾器,使我們能夠快速靈活的將功能應用於 我們的邊緣服務。這些過濾器可幫助我們執行以下功能

身份驗證和安全性:確定每個資源的身份驗證要求並拒絕不滿足要求的請求。

洞察和監控:在邊緣跟中有意義的數據和統計數據,以便爲我們提供準確的生產視圖。

動態路由:根據需要動態的將請求路由到不同的後端集羣。

壓力測試: 逐漸增加集羣的流量以衡量性能。

Load Shedding:爲每種類型的請求分配容量並刪除超過限制的請求。

靜態響應處理:直接在邊緣構建一些響應,而不是將他們轉發到內部集羣。

 四、過濾器生命週期

                      

從上圖中我們可以看到,當外部HTTP請求到達API網關服務的時候,首先他會進入第一個階段pre, 在這裏他會被pre類型的過濾器進行處理,該類型的過濾器的主要目的是在進行請求路由之前做一些前置加工,比如請求的校驗等。然後進入第二個階段routing,也就是之前我們說過的路由 請求轉發階段,請求 將會被routing 類型的過濾器進行處理。這裏的具體處理內容就是將外部請求轉發到具體服務實例上去的過程,當服務實例將請求結果返回之後,routing階段完成,請求進入第三階段post。此時,請求將會被post類型的處理器處理,這些過濾器處理的時候不僅能獲取到請求信息,還能獲取到服務實例返回信息,所以在post類型的過濾器中,我們可以對結果進行加工或轉換等內容。另外,還有一個特殊階段error,該階段只有上述三個階段中發生異常的時候纔會觸發,但是它最後流向還是post類型的過濾器,因爲它需要通過post過濾器將最終結果返回給請求客戶端(對於error過濾器處理,在Spring Cloud Zuul的過濾連中實際上有一些不同)

五、核心過濾器

在Spring Cloud zuul中,爲了讓API更爲方便的被使用,它在HTTP請求生命週期各個階段默認實現了一批過濾器,他們會在API網關啓動的時候唄自動加載和啓用。

                 

六、路由詳解

6.1 傳統路由配置

傳統路由配置是指沒有服務發現機制的情況下,通過在配置文件中具體指定每個路由表達式與服務實例的映射關係來實現API網關對外部請求的路由。

單實例配置

通過zuul.routes.<路由名>.pathzuul.routes.<路由名>.url

zuul: 
  routes: 
    service-provider: 
      path: /eureka-service/**
      url: http://localhost:8080

多實例配置

通過zuul.routes.<路由名>.pathzuul.routes.<路由名>.serviceId

zuul: 
  routes: 
    service-provider: 
      path: /eureka-service/**
      serviceId: eureka-service
ribbon: 
  eureka: 
    enabled: false
eureka-service: 
  ribbon: 
    listOfServers: http://localhost:8080/,http://localhost:8081/

它的配置和服務路由的配置方式一樣,只是服務實例列表需要我們手動維護,由於多個實例的存在,所以就需要負載均衡策略,所以這裏需要依賴ribbon配置。

6.2 服務路由配置

快速入門中已經講了面向服務路由的配置,通過與Eureka的整合,實現了對服務實例的自動維護,所以在使用服務路由的時候,無須指定serviceId所指定具體服務實例地址,只需要通過zuul.routes.<路由名>.pathzuul.routes.<路由名>.serviceId成對配置即可。

zuul: 
  routes: 
    eureka-service: 
      path: /eureka-service/**
      serviceId: eureka-service

面向服務的路由配置除了上邊的方法還有一種更簡單的方式:zuul.routes.<服務名>=<映射地址>

zuul: 
  routes: 
    eureka-service: /eureka-service/**

與傳統路由相比,有外部請求到API網關的時候,面向服務路由發生了什麼?

當有外部請求到達API網關的時候,根據請求的URL路徑去匹配path的規則,通過path找到路由名,去找對應的serviceId的服務名,

  • 傳統路由就會去根據這個服務名去找listOfServers參數,從而進行負載均衡和請求轉發
  • 面向服務路由會從註冊到服務治理框架中取出服務實例清單,通過清單直接找到對應的實例地址清單,從而通過Ribbon進行負載均衡選取實例進行路由(請求轉發)

6.3 服務路由的默認配置

Eureka與Zuul整合爲我們省去了大量的維護服務實例清單的配置工作,但是實際操作中我們會將path與serviceId都用服務名開頭,如上邊我所舉的幾個例子都是,這樣的配置其實Zuul已經默認爲我們實現了。

其實,Zuul在註冊到Eureka服務中心之後,它會爲Eureka中的每個服務都創建一個默認的路由規則,默認規則的path會使用serviceId配置的服務名作爲請求前綴。這會使一些我們不希望的開放的服務有可能被外部訪問到,此時,我們可以使用zuul.ignored-services參數來設置一個不自動創建該服務的默認路由。Zuul在自動創建服務路由的時候會根據這個表達式進行判斷,如果服務名匹配表達式,那麼Zuul將跳過此服務,不爲其創建默認路由;

zuul: 
  ignored-services: feign-customer,eureka-service

如果不想使用自動創建默認路由功能,可以使用如下方法跳過默認路由

zuul: 
  ignored-services: '*'

6.4 自定義路由映射規則

可以使用regexmapper在serviceId和路由之間提供約定。它使用正則表達式命名組從serviceId提取變量並將它們注入路由模式。

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

PatternServiceRouteMapper 對象可以讓開發者 通過正則表達式來 自定義服務與路由映射的生成關係。其中構造函數的第一個參數是用來匹配服務名稱是否符合自定義規則的正在表達式,第二個參數則是定義根據服務名稱中定義的內容轉換出的路徑表達式規則,當開發者在API網關中定義了PatternServiceRouteMapper的實現之後,只要符合 第一個參數定義的規則的服務名,都會優先使用該實現構建出的路徑表達式,如果沒有匹配成功服務規則,則還是走默認的路由映射規則,即採用完整服務名作爲前綴的路徑表達式。

6.5 路徑匹配

不論是使用傳統路由配置方式還是服務路由的配置方式,我們都需要爲每個路由規則定義匹配表達式,也就是上面說的path參數。在zuul中,路由匹配的路徑表達式採用了ant風格定義。

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

如果有一個可以同時滿足多個path的匹配的情況,此時匹配結果取決於路由規則的定義順序,

這裏需要注意的是:properties無法保證路由規則的順序,推薦使用yml格式配置文件

忽略表達式

Zuul提供了用於忽略路徑表達式的參數zuul.ignored-patterns。使用該參數可以用來設置不希望被API網關進行路由的URL表達式。

zuul: 
  ignored-patterns: /**/hello/**

路由前綴

爲了方便全局爲路由path增加前綴信息,Zuul提供了zuul.prefix參數來進行設置,但是代理前綴會從默認路徑中移除掉,爲避免這種情況,可以使用zuul.stripPrefix=false 來關閉移除代理前綴的動作,也可以通過zuul.routes.<路由名>.strip-prefix=false來指定服務關閉移除代理前綴的動作

 zuul:
  prefix: /api
  routes:
    feign-customer:
      path: /feign/**
      stripPrefix: false

6.6 Cookie與頭信息

默認情況下,Zuul在請求路由時會過濾掉HTTP請求頭信息中的一些敏感信息,防止這些敏感的頭信息傳遞到下游外部服務器。但是如果我們使用安全框架如Spring Security、Apache Shiro等,需要使用Cookie做登錄和鑑權,這時可以通過zuul.sensitiveHeaders參數定義,包括Cookie、Set-Cookie、Authorization三個屬性來使Cookie可以被傳遞。可以分爲全局指定放行Cookie和Headers信息和指定路由放行

1.全局放行

zuul: 
  sensitiveHeaders: Cookie,Set-Cookie,Authorization

2.指定路由名放行

 zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders: Cookie,Set-Cookie,Authorization
      url: https://downstream

 如果全局配置和路由配置均有不同程度的放行,那麼採取就近原則,路由配置的放行規則將生效

6.7 本地跳轉

遷移現有應用程序或API時的一種常見模式是“關閉”舊的端點,並慢慢地用不同的實現替換它們。Zuul代理是一個有用的工具,因爲可以使用它來處理來自舊端點客戶端的所有流量,但會將某些請求重定向到新端點。

zuul.routes.<路由名>.url=forward:/<要跳轉到的端點>

舉例:

 zuul:
  routes:
    first:
      path: /first/**
      url: http://first.example.com
    second:
      path: /second/**
      url: forward:/first

這樣就會將請求到/second端點的請求轉發到/first端點

七、Hystrix和Ribbon支持

上文有講過,Zuul中包含了Hystrix和Ribbon的依賴,所以Zuul擁有線程隔離和斷路器的自我保護功能,以及對服務調用的客戶端負載均衡,需要注意的是傳統路由也就是使用path與url映射關係來配置路由規則的時候,對於路由轉發的請求不會使用HystrixCommand來包裝,所以沒有線程隔離和斷路器的保護,並且也不會有負載均衡的能力。所以我們在使用Zuul的時候推薦使用path與serviceId的組合來進行配置。

7.1. 設置Hystrix超時時間

使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds來設置API網關中路由轉發請求的命令執行時間超過配置值後,Hystrix會將該執行命令標記爲TIMEOUT並拋出異常,Zuul會對該異常進行處理並返回如下JSON信息給外部調用方

{
    "timestamp":20180705141032,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.netflix.zuul.exception.ZuulException",
    "message":"TIMEOUT"
}

7.2. 設置Ribbon連接超時時間

使用ribbon.ConnectTimeout參數創建請求連接的超時時間,當ribbon.ConnectTimeout的配置值小於hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值時,若出現請求超時的時候,會自動進行重試路由請求,如果依然失敗,Zuul會返回如下JSON信息給外部調用方

{
    "timestamp":20180705141032,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.netflix.zuul.exception.ZuulException",
    "message":"NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED"
}

如果ribbon.ConnectTimeout的配置值大於hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值時,當出現請求超時的時候不會進行重試,直接超時處理返回TIMEOUT的錯誤信息

7.3. 設置Ribbon的請求轉發超時時間

使用ribbon.ReadTimeout來設置請求轉發超時時間,處理與ribbon.ConnectTimeout類似,不同點在於這是連接建立之後的處理時間。該值小於hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值時報TIMEOUT錯誤,反之報TIMEOUT的錯誤。小於的時候會先重試,不成才報錯;大於的時候直接報錯。

7.4. 關閉重試配置

  • 全局配置zuul.retryable=false
  • 針對路由配置zuul.routes.<路由名>.retryable=false

8 小結

網關在微服務中還是非常重要的一個組件,我們還是需要好好掌握的。

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