最近這段時間在使用Zuul贿堰,順便簡單閱讀了一下源碼。本文旨在對自己閱讀到的源碼做一點小結(jié)啡彬,以后日后回顧羹与。不追求面面俱到,細致入微外遇,看到哪里寫到哪里吧
關(guān)于Zuul的介紹及基本使用就不在此贅述了注簿,網(wǎng)上有很多這方面的文章
入口:
系統(tǒng)啟動時處理@EnableZuulProxy,重點是其中的ZuulProxyConfiguration.class
底層http庫:
ZuulProxyConfiguration.class又額外引入下面3個class跳仿,說明zuul支持以下3種方式作為底層http庫
1. RestClientRibbonConfiguration.class诡渴,底層是Jersey client,具體沒研究菲语,并且已經(jīng)標記為過時
2. OkHttpRibbonConfiguration.class妄辩,底層是OkHttp,安卓上用的較多山上,服務(wù)端使用情況未知
3. HttpClientRibbonConfiguration.class眼耀,底層是HttpClient,都比較熟了不多說
那么具體使用哪一種方式佩憾?我們以HttpClient舉例哮伟,看下圖源碼干花,通過ribbon.httpclient.enabled激活,并且matchIfMissing=true楞黄,說明不配置的時候作為默認激活項池凄;其他兩種方式配置也是類似,但是不作為默認激活
從源碼看鬼廓,沒有RestTemplate方式肿仑,細想一下確實也沒必要,因為RestTemplate也只是一種門面碎税,以統(tǒng)一的方式調(diào)用底層http庫尤慰,優(yōu)點就是對于使用者API更簡單和統(tǒng)一,既然不需要直接調(diào)用雷蹂,也就不需要RestTemplate了
RouteLocator:
我們繼續(xù)來看ZuulProxyConfiguration.class伟端,發(fā)現(xiàn)個DiscoveryClientRouteLocator.class
RouteLocator作用是什么?很可惜在RouteLocator.class源碼中并沒有一個整體的說明匪煌,不過從其中定義的3個方法中可以看出些端倪荔泳,其實就是維護路由信息
知道了大致的地位,我們再來看一下RouteLocator這個家族的勢力
1. RouteLocator是其鼻祖虐杯,其中定義了三個與路由相關(guān)的基本方法:獲取忽略的path集合、獲取路由列表昧港、根據(jù)path獲取路由信息
2. SimpleRouteLocator實現(xiàn)了RouteLocator及Ordered擎椰,看其描述,主要負責維護配置文件中的路由配置创肥,配置文件中的路由配置會被轉(zhuǎn)化后存儲在一個map中达舒,其中key為配置的path
3. RefreshableRouteLocator繼承自RouteLocator,額外提供了對于路由刷新方面的定義叹侄,refresh方法會結(jié)合spring的ApplicationEvent巩搏,實現(xiàn)基于事件的路由刷新機制,具體可以參看ZuulDiscoveryRefreshListener源碼
4. DiscoveryClientRouteLocator繼承自SimpleRouteLocator趾代,并且實現(xiàn)了RefreshableRouteLocator贯底,從類定義上可以看出他具備了基本的路由功能及路由刷新功能。查看源碼描述也證實了我們的猜測撒强,另外從描述中可以看出禽捆,該類會將配置文件中的路由配置(稱為靜態(tài))以及服務(wù)發(fā)現(xiàn)(比如eureka)中的路由信息進行合并。需要注意描述中的一點細節(jié)飘哨,“The discovery client takes precedence”胚想,說明會以服務(wù)發(fā)現(xiàn)中的路由信息為主
關(guān)于細節(jié),我們可以從locateRoutes方法中獲悉芽隆。從這份代碼中也就可以解答我在一開始開發(fā)時遇到的一個問題浊服,就是我僅在配置文件中配置了xxx的路由配置统屈,但是實際訪問時為什么yyy下的接口也可以被路由?原因就在于zuul會以服務(wù)發(fā)現(xiàn)中的路由信息為主牙躺,雖然自己在配置文件中沒有配置愁憔,但是eureka中卻已經(jīng)存在了40+個服務(wù)路由
5. 讓我們來到RouteLocator家族中的最后一個成員CompositeRouteLocator,他雖然僅僅實現(xiàn)了RefreshableRouteLocator述呐,但是卻是能力最強的一個惩淳,因為他能夠整合眾多RouteLocator的功能于一身。注意其構(gòu)造方法乓搬,它會將傳入的routeLocators排序思犁,還記得SimpleRouteLocator是實現(xiàn)了Ordered接口吧?
其他幾個方法就很好理解了进肯,就是遍歷排序后的routeLocators并調(diào)用相關(guān)的方法
實際上激蹲,CompositeRouteLocator也被標記為@Primary,作為主要的RouteLocator被使用江掩,CompositeRouteLocator構(gòu)造過程中被傳入的routeLocators其實僅包括一個DiscoveryClientRouteLocator
ZuulFilter:
看完RouteLocator家族弦疮,讓我們再來拜訪下另一家族ZuulFilter,從家譜上看顯然沒有RouteLocator家族人丁興旺荒辕,雖然也是爺孫三代谤草,但是卻比人家少一口(5:4)
我們先來看一下IZuulFilter,很簡單明了抬吟,shouldFilter表示該filter是否應該被運行萨咕,run方法只有在shouldFilter方法返回true時候才會被運行
ZuulFilter是一個抽象類,實現(xiàn)了IZuulFilter及Comparable火本,其中定義了幾個重要的方法:
filterType返回filter的類型危队,zuul內(nèi)置了幾個類型,包括pre钙畔、route茫陆、post、error擎析,顧名思義就知道其用途及觸發(fā)時機簿盅。不同于java web的filter,zuul filter并非嵌套調(diào)用叔锐,而是順序調(diào)用
filterOrder返回filter的排序值挪鹏,越小越靠前
介紹完這兩位,我想說的是愉烙,這個只是冰山一角讨盒,實際上spring針對zuul內(nèi)置擴展了許多核心的filter
pre:
route:
post:
看完這些,你還會覺得ZuulFilter家族勢單力薄嗎步责?另外在實際開發(fā)中返顺,我們在實現(xiàn)各種功能時絕大多數(shù)時候都是在自定義各種ZuulFilter禀苦。下面針對這些filter進行介紹
pre過濾器:
1. ServletDetectionFilter:
該過濾器order值為-3,是pre階段第一個過濾器遂鹊,并且總是會運行振乏。主要用途是判斷該請求是被spring的DispatcherServlet處理還是被zuul的ZuulServlet處理,并且將判斷結(jié)果設(shè)置到context中秉扑,后續(xù)處理中可以依照此結(jié)果進行個性化處理
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階段第四個過濾器,并且總是會運行茎活。結(jié)合Sleuth實現(xiàn)鏈路追蹤相關(guān)功能
5. DebugFilter:
該過濾器order值為1昙沦,是pre階段第五個過濾器,僅在請求參數(shù)中出現(xiàn)debug=true(參數(shù)名稱可設(shè)置)時執(zhí)行载荔。具體執(zhí)行邏輯就是在context中設(shè)置debugRouting=true及debugRequest=true桅滋。在后續(xù)執(zhí)行中可以通過這兩個值來預埋一些debug信息,用于出現(xiàn)問題時提供排查所需的信息
6. PreDecorationFilter:
該過濾器order值為5身辨,是pre階段第六個過濾器(最后一個),僅在forward.to和serviceId都沒有出現(xiàn)在context中的時候才執(zhí)行芍碧。具體來說就是對請求做一些預處理煌珊,包括使用前面介紹過的RouteLocator獲取路由信息,在context中設(shè)置一些后續(xù)處理需要的信息泌豆,還有就是在請求頭中添加一些代理信息定庵,比如X-Forwarded-For
route過濾器:
1. RibbonRoutingFilter:
該過濾器order值為10,是route階段第一個過濾器踪危,僅在context中存在serviceId的情況下運行蔬浙。存在serviceId,就是說需要面向服務(wù)進行路由贞远,服務(wù)的路由信息就是我們上面講過的兩種方式畴博,配置文件(靜態(tài))及服務(wù)注冊。具體就是創(chuàng)建RibbonCommandContext蓝仲,然后交由ribbon和hystrix向下游服務(wù)進行請求
2. SimpleHostRoutingFilter:
該過濾器order值為100俱病,是route階段第二個過濾器官疲,僅在context中存在routeHost的情況下運行。存在routeHost亮隙,就是說我們配置了具體的http或者https url的請求信息途凫。具體邏輯就是通過HttpClient直接向目標url發(fā)起請求,不再經(jīng)過ribbon及hystrix溢吻,所以也就沒有負載均衡以及熔斷
3. SendForwardFilter:
該過濾器order值為500维费,是route階段第三個(最后一個)過濾器,僅在context中存在forward.to的情況下運行促王。存在forward.to犀盟,就是說我們配置了類似forward:/index的請求信息。具體就是通過RequestDispatcher進行forward
post過濾器:
1. TracePostZuulFilter:
該過濾器order值為0硼砰,是post階段第一個過濾器且蓬,結(jié)合pre階段的TracePreZuulFilter通過Sleuth實現(xiàn)鏈路追蹤的功能
2. SendResponseFilter:
該過濾器order值為1000,是post階段第二個(最后一個)過濾器题翰,僅在context中存在zuulResponseHeaders恶阴、responseDataStream、responseBody(三者是或的關(guān)系)的情況下運行豹障,簡單來說冯事,就是在有響應數(shù)據(jù)的時候運行。我們以responseBody舉例血公,來看下responseBody是什么時候被設(shè)置到context中的昵仅。還記得RibbonRoutingFilter吧,在他的run方法中會調(diào)用一個setResponse方法累魔,responseBody就是在這個方法中被設(shè)置到context中
再回到SendResponseFilter摔笤,具體邏輯就是添加相關(guān)的響應頭,然后將響應數(shù)據(jù)回寫到response
error過濾器:
1. SendErrorFilter:
該過濾器order值為0垦写,是error階段唯一一個過濾器吕世,僅在context中存在throwable的情況下運行,也就是說有異常產(chǎn)生的情況下運行梯投。那么這個throwable是何時被設(shè)置到context中的呢命辖?我們不妨看一下ZuulServlet,service方法中分蓖,preRoute尔艇、route、postRoute分別負責上述三種過濾器的運行么鹤,他們外層被try catch终娃,產(chǎn)生異常的時候會執(zhí)行error方法,throwable就是在error方法中被設(shè)置到context
繼續(xù)回到SendErrorFilter蒸甜,他的執(zhí)行邏輯也比較簡單尝抖,就是將錯誤狀態(tài)碼毡们、錯誤信息、異常對象設(shè)置到request中昧辽,然后forward到/error(默認衙熔,可配置)。之后我們可以自己定義一個/error端口對錯誤進行響應
了解完上面這些重要的組件后搅荞,讓我們再來看一下红氯,當一個請求到來時,zuul是如何利用這些組件咕痛,將一切串起來的
ZuulContoller:
ZuulContoller繼承自spring的ServletWrappingController痢甘,能夠?qū)⒄埱蠼挥蒢uulServlet處理
ZuulServlet:
ZuulServlet接收到請求,初始化RequestContext茉贡,之后分別執(zhí)行pre塞栅、route和post階段的filter,最后銷毀context
ZuulRunner:
可以看到腔丧,ZuulServlet實際只是組織整個流程放椰,具體工作還是交給ZuulRunner來完成,看一下ZuulRunner的描述:初始化context愉粤,并將request砾医、response設(shè)置其中,并且通過FilterProcessor調(diào)用pre衣厘、route如蚜、post及error
FilterProcessor:
Filter的核心執(zhí)行類,根據(jù)不同的filter類型運行filter
最后給出一個上述步驟的簡化時序圖: