Zuul源碼解讀

最近這段時間在使用Zuul,順便簡單閱讀了一下源碼。本文旨在對自己閱讀到的源碼做一點小結,以後日後回顧。不追求面面俱到,細緻入微,看到哪裏寫到哪裏吧

關於Zuul的介紹及基本使用就不在此贅述了,網上有很多這方面的文章

入口:

系統啓動時處理@EnableZuulProxy,重點是其中的ZuulProxyConfiguration.class


底層http庫:

ZuulProxyConfiguration.class又額外引入下面3個class,說明zuul支持以下3種方式作爲底層http庫


1. RestClientRibbonConfiguration.class,底層是Jersey client,具體沒研究,並且已經標記爲過時


2. OkHttpRibbonConfiguration.class,底層是OkHttp,安卓上用的較多,服務端使用情況未知

3. HttpClientRibbonConfiguration.class,底層是HttpClient,都比較熟了不多說

那麼具體使用哪一種方式?我們以HttpClient舉例,看下圖源碼,通過ribbon.httpclient.enabled激活,並且matchIfMissing=true,說明不配置的時候作爲默認激活項;其他兩種方式配置也是類似,但是不作爲默認激活


從源碼看,沒有RestTemplate方式,細想一下確實也沒必要,因爲RestTemplate也只是一種門面,以統一的方式調用底層http庫,優點就是對於使用者API更簡單和統一,既然不需要直接調用,也就不需要RestTemplate了

RouteLocator:

我們繼續來看ZuulProxyConfiguration.class,發現個DiscoveryClientRouteLocator.class


RouteLocator作用是什麼?很可惜在RouteLocator.class源碼中並沒有一個整體的說明,不過從其中定義的3個方法中可以看出些端倪,其實就是維護路由信息


知道了大致的地位,我們再來看一下RouteLocator這個家族的勢力


1. RouteLocator是其鼻祖,其中定義了三個與路由相關的基本方法:獲取忽略的path集合、獲取路由列表、根據path獲取路由信息

2. SimpleRouteLocator實現了RouteLocator及Ordered,看其描述,主要負責維護配置文件中的路由配置,配置文件中的路由配置會被轉化後存儲在一個map中,其中key爲配置的path


3. RefreshableRouteLocator繼承自RouteLocator,額外提供了對於路由刷新方面的定義,refresh方法會結合spring的ApplicationEvent,實現基於事件的路由刷新機制,具體可以參看ZuulDiscoveryRefreshListener源碼


4. DiscoveryClientRouteLocator繼承自SimpleRouteLocator,並且實現了RefreshableRouteLocator,從類定義上可以看出他具備了基本的路由功能及路由刷新功能。查看源碼描述也證實了我們的猜測,另外從描述中可以看出,該類會將配置文件中的路由配置(稱爲靜態)以及服務發現(比如eureka)中的路由信息進行合併。需要注意描述中的一點細節,“The discovery client takes precedence”,說明會以服務發現中的路由信息爲主


關於細節,我們可以從locateRoutes方法中獲悉。從這份代碼中也就可以解答我在一開始開發時遇到的一個問題,就是我僅在配置文件中配置了xxx的路由配置,但是實際訪問時爲什麼yyy下的接口也可以被路由?原因就在於zuul會以服務發現中的路由信息爲主,雖然自己在配置文件中沒有配置,但是eureka中卻已經存在了40+個服務路由



5. 讓我們來到RouteLocator家族中的最後一個成員CompositeRouteLocator,他雖然僅僅實現了RefreshableRouteLocator,但是卻是能力最強的一個,因爲他能夠整合衆多RouteLocator的功能於一身。注意其構造方法,它會將傳入的routeLocators排序,還記得SimpleRouteLocator是實現了Ordered接口吧?


其他幾個方法就很好理解了,就是遍歷排序後的routeLocators並調用相關的方法


實際上,CompositeRouteLocator也被標記爲@Primary,作爲主要的RouteLocator被使用,CompositeRouteLocator構造過程中被傳入的routeLocators其實僅包括一個DiscoveryClientRouteLocator


ZuulFilter:

看完RouteLocator家族,讓我們再來拜訪下另一家族ZuulFilter,從家譜上看顯然沒有RouteLocator家族人丁興旺,雖然也是爺孫三代,但是卻比人家少一口(5:4)


我們先來看一下IZuulFilter,很簡單明瞭,shouldFilter表示該filter是否應該被運行,run方法只有在shouldFilter方法返回true時候纔會被運行


ZuulFilter是一個抽象類,實現了IZuulFilter及Comparable,其中定義了幾個重要的方法:

filterType返回filter的類型,zuul內置了幾個類型,包括pre、route、post、error,顧名思義就知道其用途及觸發時機。不同於java web的filter,zuul filter並非嵌套調用,而是順序調用


filterOrder返回filter的排序值,越小越靠前


介紹完這兩位,我想說的是,這個只是冰山一角,實際上spring針對zuul內置擴展了許多核心的filter

pre:


route:


post:


看完這些,你還會覺得ZuulFilter家族勢單力薄嗎?另外在實際開發中,我們在實現各種功能時絕大多數時候都是在自定義各種ZuulFilter。下面針對這些filter進行介紹

pre過濾器:

1. ServletDetectionFilter:

該過濾器order值爲-3,是pre階段第一個過濾器,並且總是會運行。主要用途是判斷該請求是被spring的DispatcherServlet處理還是被zuul的ZuulServlet處理,並且將判斷結果設置到context中,後續處理中可以依照此結果進行個性化處理


2. Servlet30WrapperFilter:

該過濾器order值爲-2,是pre階段第二個過濾器,並且總是會運行。Zuul默認僅對servlet2.5兼容,該過濾器可以將request包裝成3.0兼容的形式(Servlet30RequestWrapper)


3. FormBodyWrapperFilter:

該過濾器order值爲-1,是pre階段第三個過濾器,僅針對兩類請求生效,第一種是Context-Type爲application/x-www-form-urlencoded,第二種是由spring的DispatcherServlet處理的Context-Type爲multipart/form-data的請求。該過濾器的主要目的是將上述兩種請求包裝成FormBodyRequestWrapper



4. TracePreZuulFilter:

該過濾器order值爲0,是pre階段第四個過濾器,並且總是會運行。結合Sleuth實現鏈路追蹤相關功能


5. DebugFilter:

該過濾器order值爲1,是pre階段第五個過濾器,僅在請求參數中出現debug=true(參數名稱可設置)時執行。具體執行邏輯就是在context中設置debugRouting=true及debugRequest=true。在後續執行中可以通過這兩個值來預埋一些debug信息,用於出現問題時提供排查所需的信息


6. PreDecorationFilter:

該過濾器order值爲5,是pre階段第六個過濾器(最後一個),僅在forward.to和serviceId都沒有出現在context中的時候才執行。具體來說就是對請求做一些預處理,包括使用前面介紹過的RouteLocator獲取路由信息,在context中設置一些後續處理需要的信息,還有就是在請求頭中添加一些代理信息,比如X-Forwarded-For



route過濾器:

1. RibbonRoutingFilter:

該過濾器order值爲10,是route階段第一個過濾器,僅在context中存在serviceId的情況下運行。存在serviceId,就是說需要面向服務進行路由,服務的路由信息就是我們上面講過的兩種方式,配置文件(靜態)及服務註冊。具體就是創建RibbonCommandContext,然後交由ribbon和hystrix向下遊服務進行請求


2. SimpleHostRoutingFilter:

該過濾器order值爲100,是route階段第二個過濾器,僅在context中存在routeHost的情況下運行。存在routeHost,就是說我們配置了具體的http或者https url的請求信息。具體邏輯就是通過HttpClient直接向目標url發起請求,不再經過ribbon及hystrix,所以也就沒有負載均衡以及熔斷


3. SendForwardFilter:

該過濾器order值爲500,是route階段第三個(最後一個)過濾器,僅在context中存在forward.to的情況下運行。存在forward.to,就是說我們配置了類似forward:/index的請求信息。具體就是通過RequestDispatcher進行forward


post過濾器:

1. TracePostZuulFilter:

該過濾器order值爲0,是post階段第一個過濾器,結合pre階段的TracePreZuulFilter通過Sleuth實現鏈路追蹤的功能


2. SendResponseFilter:

該過濾器order值爲1000,是post階段第二個(最後一個)過濾器,僅在context中存在zuulResponseHeaders、responseDataStream、responseBody(三者是或的關係)的情況下運行,簡單來說,就是在有響應數據的時候運行。我們以responseBody舉例,來看下responseBody是什麼時候被設置到context中的。還記得RibbonRoutingFilter吧,在他的run方法中會調用一個setResponse方法,responseBody就是在這個方法中被設置到context中



再回到SendResponseFilter,具體邏輯就是添加相關的響應頭,然後將響應數據回寫到response


error過濾器:

1. SendErrorFilter:

該過濾器order值爲0,是error階段唯一一個過濾器,僅在context中存在throwable的情況下運行,也就是說有異常產生的情況下運行。那麼這個throwable是何時被設置到context中的呢?我們不妨看一下ZuulServlet,service方法中,preRoute、route、postRoute分別負責上述三種過濾器的運行,他們外層被try catch,產生異常的時候會執行error方法,throwable就是在error方法中被設置到context



繼續回到SendErrorFilter,他的執行邏輯也比較簡單,就是將錯誤狀態碼、錯誤信息、異常對象設置到request中,然後forward到/error(默認,可配置)。之後我們可以自己定義一個/error端口對錯誤進行響應


瞭解完上面這些重要的組件後,讓我們再來看一下,當一個請求到來時,zuul是如何利用這些組件,將一切串起來的

ZuulContoller:

ZuulContoller繼承自spring的ServletWrappingController,能夠將請求交由ZuulServlet處理


ZuulServlet:

ZuulServlet接收到請求,初始化RequestContext,之後分別執行pre、route和post階段的filter,最後銷燬context




ZuulRunner:

可以看到,ZuulServlet實際只是組織整個流程,具體工作還是交給ZuulRunner來完成,看一下ZuulRunner的描述:初始化context,並將request、response設置其中,並且通過FilterProcessor調用pre、route、post及error




FilterProcessor:

Filter的核心執行類,根據不同的filter類型運行filter




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