Zuul(API網關)學習筆記

一、Zuul簡介

1.1、Zuul是什麼

Zuul是Netflix開源的API網關。

API網關:類似於面向對象設計模式中的Facade模式,它存在就像是整個微服務系統的門面一樣,所有的外部客戶端訪問都需要經過它來進行調度和過濾,它除了要實現請求路由、負載均衡、來源合法性檢測、權限校驗、反爬蟲等功能之外,還需要更多能力,比如與服務治理框架的結合,請求轉發時的熔斷機制,服務的聚合等一系列的高級功能。

1.2、Zuul能幹什麼

1.2.1、身份驗證和安全

識別每個請求的身份驗證,並拒絕不滿足的請求。

1.2.2、審查和監測

跟蹤邊緣的有意義數據和統計數據,以便我們準確地瞭解系統及業務運行情況。

1.2.3、動態路由

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

1.2.4、壓力測試

逐漸增加到集羣的流量,以衡量性能。

1.2.5、負載分配

爲每種類型的請求分配容量,並刪除超出限制的請求。

1.2.6、靜態響應處理

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

1.3、Zuul有什麼

Zuul包含了對請求的路由過濾這兩個最主要的功能:

1.3.1、路由

路由功能負責將外部請求轉發到具體的微服務實例上,是實現外部訪問統一入口的基礎。

1.3.2、過濾

過濾功能則負責對請求的處理過程進行干預,是實現請求驗證、服務聚合等功能的基礎。

實際上,路由功能在真正運行時,它的路由映射和請求轉發都是由幾個不同的過濾器完成的。其中,路由映射主要通過pre類型的過濾器完成,它將請求路徑與配置的路由規則進行匹配,以找到需要轉發的目標地址;而請求轉發的部分則是有route類型的過濾器來完成,對pre類型的過濾器獲得路由地址並進行轉發。

二、Zuul的Hello World

2.1、加入依賴

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

2.2、加入配置

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ 
server.port=8780
spring.application.name=service-zuul
zuul.routes.api-a.path=/aa/** 
zuul.routes.api-a.serviceId=userservice
zuul.routes.api-b.path=/bb/** 
zuul.routes.api-b.serviceId=service-feign

注意:serviceId需要全部小寫

2.3、啓動類加入註解

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication

說明:
1、@EnableEuulProxy:開啓Zuul服務,將本地調用轉發到響應的服務,Zuul使用Ribbon來定位一個要轉發的服務實例,並且所有請求都以hystrix命令執行。
2、注意: @EnableZuulProxy不包括髮現服務的客戶端,因此對於基於服務ID的理由,還需要提供服務治理(例如Eureka)
3、使用@EnableZuulServer也可以運行不帶代理的Zuul服務器,差別是它不會自動添加任何代理過濾器,也沒有服務發現和代理。

2.4、測試運行

1、如果配置文件裏沒有配置zuul的話,zuul會把Eureka Server裏面的服務都讀取出來,此時運行的時候,需要用各個服務的serviceId作爲ContextPath。

2、如果配置了zuul,還可以用配置的前綴代替服務的serviceId,然後進行訪問。

3、如果不希望直接使用serviceId進行訪問,可以配置ignored.services: ‘*’,例如:

zuul.ignored.services='*'

當然可以不用*, 而是直接把要禁用的serviceId羅列出來,用逗號分開。

三、路由配置

3.1、傳統路由配置

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

3.3.1、單實例配置

通過zuul.routes.<route>.path與zuul.routes.<route>.url參數的方式進行配置,如:

zuul.routes.dd.path=/dd/**
zuul.routes.dd.url=http://localhost:8769

3.3.2、多實例配置

通過zuul.routes.<route>.path與zuul.routes.<routes>.serviceId參數的方式進行配置,結合Ribbon來做負載均衡,且讓Ribbon關閉和Eureka的結合,比如:

zuul.routes.dd.path=/dd/**
#zuul.routes.dd.url=http://lcoalhost:8769
zuul.routes.dd.serviceId=ddservice

ribbon.eureka.enabled=false
ddservice.ribbon.listOfServers=http://localhost:8769,http://localhost:8768

這種簡單的URL路由不會被執行爲HystrixCommand,也不能使用Ribbon對多個URL進行負載均衡。

3.2、服務路由配置

Spring Cloud Zuul整合了Spring Cloud Eureka,實現了對服務實例的自動化維護,那麼使用服務路由配置的時候,不需要向傳統路由配置方式那樣爲serviceId指定具體服務實例地址,值需要通過zuul.routes.<route>.path與zuul.routes.<route>.serviceId參數對的方式,進行配置即可。

還可以簡化配置,直接設置<route>爲具體的serviceId,例如:

zuul.routes.service.feign.path=/dd/**

請記得開啓前面實例中關閉的Ribbon和Eureka的結合。

3.2.1、服務路由的默認規則

大部分路由規則機會都會採用服務名作爲外部請求的前綴,zuul已經自動幫我們實現了以服務名作爲前綴的映射,不需要去配置它。

如果不希望外部能直接訪問,可以使用zuul.ignored.services參數來設置一個服務名匹配表達式來定義不自動創建路由的規則。

3.2.2、自定義路由映射關係

有時候,爲了兼容外部不同版本的客戶端程序,我們需要在微服務接口上定義版本的標記,比如:service-v1, service-v2,而這一在映射的時候不太好管理。

通常的做法是,爲這些不同版本的微服務應用,生成以版本號爲路由前綴定義規則的路由規則,比如/v1/userservice, 具體示例入下:

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

注意:當存在多個匹配的路由規則時,匹配結果完全取決於路由規則的定義順序。由於properties的配置內容無法保證有序,所以當出現這樣的情況時,爲了保證路由的優先順序,需要使用yml文件來配置,以實現有序的路由規則。

3.2.3、Cookie與頭信息

默認情況下,spring cloud zuul在請求路由時,會過濾掉http請求頭信息中一些敏感信息,防止他們被傳遞到下游的外部服務器。

默認敏感的頭部信息通過zuul.sensitiveHeaders參數定義,默認包括cookie, set-Sookie, authorization三個屬性。

這會引發一個場景的問題,使用了spring security, shiro等安全框架構建的web應用,通過spring cloud zuul構建網關來進行路由時,由於cookie信息無法傳遞,web應用將無法實現登陸和鑑權。爲了解決這個問題,配置的方法可以爲:
1)通過設置全局參數來覆蓋默認值,如:
zuul.sensitiveHeaders=
不推薦這種做法,範圍太大

2)將制定路由的敏感頭設置爲空,如:
zuul.routes.userservice.sensitiveHeaders=

3.2.4、重定向

使用API網關的一個重要原因就是將網關作爲統一入口,從而不暴露所有內部服務細節。但我們在應用內部調整的URL卻是具體的web應用實例的地址,而不是通過網關的路由地址,該問題的根本原因在於spring cloud zuul在路由請求時,並沒有將最初的host信息設置正確,該怎麼辦呢?
配置zuul.add-host-header=true即可。

3.2.5、路由回退

3.2.5.1、Hystrix和Ribbon支持

spring cloud zuul默認集成了hystrix和Ribbon,自然就擁有線程隔離和斷路器,以及對服務調用的客戶端負載均衡功能。

需要注意的是,當使用path與url的映射關係來配置路由規則的時候,對於路由轉發的請求不會採用hystrixCommand來包裝,所以這類請求沒有線程隔離和斷路器的保護,並且也不會有負載均衡的能力。因此,我們在使用zuul的時候,儘量使用path與serviceId的組合來進行配置,這樣不僅可以保證API網關的健壯與穩定,也能用到ribbon的客戶端負載均衡功能。

所有路由的默認hystrix隔離模式(ExecutionsolationStrategy)爲SUMAPHORE。

3.2.5.2、Hystrix的路由回退

可以通過創建ZuulFallbackProvider類型的bean來提供回退響應。
zuul首先會去判斷是否存在自定義的zuulFallbackProvider,如果有,那麼直接回調你自定義實現類的fallbackResponse(), 但是這個方法是無參的,也就是說此時雖然你能夠給調用端返回一個消息,但此時你並不知道發送了什麼樣的異常(也就是說這裏你是獲取不到異常信息的)

3.2.6、請求路由其他配置

3.2.6.1、路由前綴

zuul.prefix:爲路由規則增加前綴,例如:zuul.prefix=/user
zuul.strip.prefix(默認爲true):這個設置是在轉發請求之前,從請求中刪除代理前綴

3.2.6.2、本地跳轉

通過在serviceId中使用forward來指定需要跳轉的服務器資源路徑,例如:
zuul.routes.api-t.path=/tt/**
zuul.routes.api-t.serviceId=forward:/tt

3.2.6.3、忽略表達式

zuul.ignored.patterns:設置不希望被API網關進行路由的url表達式。例如:
ignoredPatterns: //products/
注意:它的範圍並不是真毒某個路由,而是對所有路由,所以要小心使用。

3.2.6.4、路由端點

使用@EnableZuulProxy,將啓用/routes端點,可返回映射路由的列表。
可以通過將endpoints.routes.enabled設置爲false來禁用此端點。

配置屬性zuul.max.host.connections已被兩個新屬性zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections替換,分別默認爲200和20

3.2.6.5、性能優化

zuul內部路由可以理解爲使用一個線程池去發送路由請求,所以我們也需要擴大這個線程池的容量,如:
zuul.host.max-per-route-connections=1000
zuul.host.max-total-connections=1000

四、請求過濾器

4.1、請求過濾器概述

加入zuul做網關服務後,外部客戶端訪問我們的系統已經有了統一的入口,那麼那些與具體的業務無關的功能,應該在請求到達的時候就完成,比如:校驗和過濾,這就涉及到了zuul的另一個主要功能:請求過濾。

定義一個過濾器
把這個過濾器加入spring容器並使用。

4.2、請求過濾器的高層架構

在這裏插入圖片描述

4.2.1、ZuulServlet

zuul的核心是一系列的filters,zuul大部分功能都是通過過濾器來實現的。

1、zuulServlet是zuul的核心類,用來調度不同階段的filters,處理請求,並處理異常等,路徑是/zuul,可以使用zuul.servlet-path屬性更改此路徑。

2、功能類似於spring mvc中的DispatcherServlet,所有的Request都要經過它的處理。

3、裏面有三個核心方法:preRoute(), route(), postRoute()

4、ZuulServlet會把具體的執行交給ZuulRunner去做,ZuulServlet是單例,因此ZuulRunner也僅有一個實例。

5、Zuul的過濾器直接沒有直接的相互通信,他們之間通過一個RequestConext的靜態類來進行數據傳遞。RequestContext類中有ThreadLocal變量來記錄每個Request所需要傳遞的數據,ZuulRequest會初始化RequestContext。

6、ZuulRunner直接將執行邏輯交給FilterProcessor處理,FilterProcesser也是單例,其功能就是根據filterType執行filter的處理邏輯,大致過程如下:
1)根據Type獲取所有輸入該Type的filter。
2)遍歷執行每個filter的處理邏輯,processZuulFilter(ZuulFilter filter)
3)RequestContext對每個filter的執行狀況進行記錄,如果執行失敗,則對異常封裝後拋出。

4.2.2、請求生命週期

在這裏插入圖片描述

4.2.2.1、過濾器類型與生命週期

PRE:這種過濾器在請求被路由之前調用。可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。
ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用 Apache HttpClient或Netflix Ribbon請求微服務。
POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來爲響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
ERROR:在其他階段發生錯誤時,執行該過濾器。

一般來說,如果需要在請求到達後端應用前,就進行處理的話,會選擇前置過濾器,例如鑑權、請求轉發、增加請求參數等行爲。在請求完成後,需要處理的操作放在後置過濾器中完成,例如統計返回值和調用時間、記錄日誌、增加跨域頭等行爲。

路由過濾器一般只需要選擇zuul中內置的即可,錯誤過濾器一般只需要一個,這樣可以在Gateway遇到錯誤邏輯時,直接拋出異常中斷流程,並直接統一處理返回結果。

4.3、核心過濾器

4.3.1、概述

在spring cloud zuul中,爲了讓API網關組件可以被更方便地使用,它在HTTP請求聲明週期的各個階段默認實現了一批覈心過濾器,他們會在API網關服務啓動的時候,被自動加載和啓動。

默認spring-cloud-netflix-core模塊org.springframework.cloud.netflix.zuul.filters包下。

4.3.2、pre過濾器

1、ServletDetectionFilter

用來檢測當前請求是通過spring的DispatcherServlet處理運行的,還是通過ZuulServlet來處理運行的。它的檢測結果會議布爾類型保存在當前請求上下文的isDispatcherServletRequest參數中,這樣後續的過濾器中,可以通過RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法來判定請求處理的源頭,以實現後續不同的處理機制。

一般情況下,發送到API網關的外部請求都會被spring的DispatcherServlet處理,除了通過/zuul/*路徑訪問的請求會繞過DispatcherServlet,被ZuulServlet處理。

2、FormBodyWrapperFilter

解析表單數據,並對下游請求進行重新編碼。
該過濾器僅對兩類請求生效,第一類是Context-Type爲appliction/x-www-form-urlencoded的請求,第二類是Context-Type爲multipart/form-data並且是由spring的DispatcherServlet處理的請求,而該過濾器的主要目的是將符合要求的請求體包裝成FormBodyRequestWrapper對象。

3、DebugFilter

該過濾器會根據配置參數zuul.debug.request和請求中的debug參數來決定是否執行過濾器中的操作。

它的具體操作內容是將當前請求上下文中的debugRouting和debugRequest參數設置爲true。由於在同一個請求的不同聲明週期都可以訪問到這兩個值,所以我們在後續的各個過濾器中可以利用這兩個值來定義一些debug信息,這樣當線上環境出現問題的時候,可以通過參數的方式來激活這些debug信息以幫助分析問題,另外,對於請求參數中的debug參數,我們可以通過zuul.debug.parameter來進行自定義。

4、PreDecorationFilter

此過濾器根據提供的RouteLocator確定在哪裏和如何路由。
該過濾器會判斷當前請求上下文中是否存在forward.do和serviceId參數,如果都不存在,那麼他就執行具體過濾器的操作(如果有一個存在的話,說明當前請求已經被處理過了,因爲第二個信息就是根據當前請求的路由信息加載儘量的)。
另外,還可以在該實心中對http頭請求進行處理的邏輯,其中包含了一些耳熟能詳的頭域,比如X-Forwarded-Host, X-Forwarded-Port。對於這些頭域,是通過zuul.addProxyHeaders參數進行控制的,而這個參數默認值是true,所以zuul在請求調整時會默認爲請求增加X-Forward-*頭域,包括X-Forwarded-Host, X-Forwarded-Post, X-Forwarded-For, X-Forwarded-Prefix, X-Forwarded-Proto。也可以通過設置zuul.addProxyHeaders=false關閉對這些頭域的添加動作。

4.3.3、route過濾器

1、RibbonRoutingFilter

該過濾器值對請求上下文中存在serviceId參數的請求進行處理,即只對通過serviceId配置路由規則的請求生效。
該過濾器的執行邏輯就是面向服務路由的核心,它通過使用ribbon和hystrix來向服務實例發起請求,並將服務實例的請求結果返回。

2、SimpleHostRoutingFilter

該過濾器只對請求上下文存在routeHost參數的請求進行處理,即只通過URL配置路由規則的請求生效。
該過濾器的執行邏輯就是直接向routeHost參數的物理地址發起請求,從源碼中我們可以知道該請求時直接通過httpclient包實現的,而沒有使用hystrix命令進行包裝,所以這類請求並沒有線程隔離和斷路器保護。

3、SendForwardFilter

該過濾器只對請求上下文中存在forward.do參數進行處理請求,即用來處理路由規則中的forward本地跳轉裝配。

4.3.4、post過濾器

1、SendErrorFilter

該過濾器僅在請求上下文中包含error.status_cdoe參數(由之前執行的過濾器設置的錯誤編碼)並且還沒有被該過濾器處理過的時候執行。
該過濾器的具體執行邏輯就是利用上下文中的錯誤信息來組成一個forward到API網關/error錯誤斷掉的請求來產生錯誤響應。
可以通過error.path屬性來更改默認轉發路徑(/error).

2、SendResponseFilter

該過濾器會檢測請求上下文中是否包含請求響應相關的頭信息,響應數據流或是響應體,只有在包含他們其中一個的時候執行處理邏輯。
其處理邏輯就是利用上下文的響應信息來阻止需要發送回客戶端的響應內容。

4.3.5、禁用過濾器

1、重寫shouldFilter邏輯,讓他返回false
2、通過配置來禁用:
zuul.<SimpleClassName>.<filterType>.disable=true
<SimpleClassName>代表過濾器的類名,<filterType>代表過濾器類型。

4.4、異常處理

過於過濾器的處理流程中發生異常,通常的處理方式:

1、使用try-catch,在每個過濾器當中進行處理,然後轉拋出ZuulRuntimeException,異常會到達SendErrorFilter裏面進行處理。

2、可以自己寫一個類來繼承SendErrorFilter,在裏面進行處理。

4.5、重試機制

1、zuul的重試機制是依賴Spring-Retry的,因此pom.xml必須有spring-retry:

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

2、開啓重試,也可以具體的爲某個服務開啓重試
zuul.routes.user-api.retryable=true
其中的user-api是路由名稱,可自行自定義。

3、然後是相應的hystrix和ribbon配置:

hystrix.command.default.execution.timeout.enabled=true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
ribbon.ConnectTimeout=250 ribbon.ReadTimeout=1000 ribbon.OkToRetryOnAllOperations=true ribbon.MaxAutoRetries=0 ribbon.MaxAutoRetriesNextServer=1

4、關閉重試機制:
全局關閉:zuul.retryable=false
指定路由關閉:zuul.routes.<route>.retryable=false

五、餓漢式加載

zuul內部使用Ribbon調用遠程的URL,並且Ribbon客戶端默認在第一次調用時由spring cloud加載,可以使用以下配置更改zuul的此行爲:
zuul.ribbon.eager.load.enabled=true

在spring cloud zuul的飢餓加載中,沒有涉及專門的參數來配置,而直接採用了讀取路由配置來進行飢餓加載的做法。所以,如果我們使用默認路由,而沒有通過配置的方式指定具體的路由規則,那麼zuul.ribbon.eager.load.enabled=true的設置就沒有什麼作用了。

因此,在真正使用的時候,可以通過zuul.ignored-services=*來忽略所有的默認路由,讓所有路由配置均維護在配置文件中,以達到網關啓動的時候就默認初始化好各個路由轉發的拒載均衡對象。

六、上傳文件

對於小文件,zuul不用做任何特別配置,直接按照路徑進行路由就可以了;
對於大文件,一個簡單的方案就是使用在你的路徑前添加上:/zuul/,來繞開spring的DispatcherServlet,以避免多部分處理,同時也避免了後臺提取中文名亂碼的問題。

1、要設置真正處理文件上傳的應用,設置允許大文件上傳,默認最大是10M,如:
spring.http.multipart.enabled=true
spring.http.multipart.max-file-size=1000Mb
spring.http.multipart.max-request-size=1500Mb

2、要設置zuul應用,注意是超時的問題,如:
zuul.host.socket-timeout-millis=10000
zull.host.connect-timeout-millis=10000

hystrix.command.default.execution.isolation.thread.timeoutMilliseconds=15000

ribbon.ConnectTimeout=500
ribbon.ReadTimeout=15000

七、健康檢查

1、加入依賴:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId> 
</dependency>

2、配置示例

# 開啓健康檢測(需要spring-boot-starter-actuator依賴)
eureka.client.healthcheck.enable=true
# 租期到期時間,默認90秒
eureka.instance.lease-expiration-duration-in-senconds=30
# 租賃更新時間間隔,默認30,即30秒發送一次心跳
eureka.instance.lease-renewal-interval-in-seconds=10

#hystrix dashboard的信息收集頻率,默認500毫秒,設置dashboard的刷新頻率
hystrix.stream.dashboard.intervalInMillseconds=5000
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章