Zuul源碼解讀

最近這段時間在使用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

最后給出一個上述步驟的簡化時序圖:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末影暴,一起剝皮案震驚了整個濱河市错邦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌型宙,老刑警劉巖兴猩,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異早歇,居然都是意外死亡,警方通過查閱死者的電腦和手機讨勤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門箭跳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人潭千,你說我怎么就攤上這事谱姓。” “怎么了刨晴?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵屉来,是天一觀的道長路翻。 經(jīng)常有香客問我,道長茄靠,這世上最難降的妖魔是什么茂契? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮慨绳,結(jié)果婚禮上掉冶,老公的妹妹穿的比我還像新娘。我一直安慰自己脐雪,他們只是感情好厌小,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著战秋,像睡著了一般璧亚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脂信,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天癣蟋,我揣著相機與錄音,去河邊找鬼吉嚣。 笑死梢薪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尝哆。 我是一名探鬼主播秉撇,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秋泄!你這毒婦竟也來了琐馆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤恒序,失蹤者是張志新(化名)和其女友劉穎瘦麸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歧胁,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡滋饲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喊巍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屠缭。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖崭参,靈堂內(nèi)的尸體忽然破棺而出呵曹,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布奄喂,位于F島的核電站铐殃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跨新。R本人自食惡果不足惜富腊,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玻蝌。 院中可真熱鬧蟹肘,春花似錦、人聲如沸俯树。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽许饿。三九已至阳欲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陋率,已是汗流浹背球化。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓦糟,地道東北人筒愚。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像菩浙,于是被迫代替她去往敵國和親巢掺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容