原始碼基於 spring-cloud-netflix-zuul-2.2.6.RELEASE.jar
需要具備SpringMVC原始碼功底 推薦學習https://www.cnblogs.com/cuzzz/p/16538064.html
零丶概述
Zuul是netflix旗下開源閘道器,作為微服務系統的閘道器元件,是微服務請求的前門。微服務閘道器的作用有點類似於AOP
,將負載均衡
,限流
,熔斷
等"橫切關注點"都集中於此,避免每一個服務都需要寫重複的功能(解決不了的問題,我們就加一層doge)
且微服務閘道器是統一入口
,系統有多個服務,不能每個服務一個對外地址,使用者需要一個統一的系統入口進行操作,故zuul閘道器是系統的統一入口。
為微服務雲平臺提供統一的入口是API閘道器最主要的用途,除此之外,閘道器還可承擔認證授權、訪問控制、路由、負載均衡、快取、日誌、限流限額、轉換、對映、過濾、熔斷、註冊、服務編排、API管理、監控、統計分析等等非業務性的功能。
下面是zuul的架構圖,結合圖我們開始Zuul原始碼之旅。
一丶ZuulHandlerMapping 處理器對映器
zuul和spring的結合,請求到達服務的時候,tomcat會根據請求路徑呼叫到指定的servlet,這裡會呼叫到DispatcherServlet
,DispatcherServlet
會使用HandlerMapping處理器對映器找到當前請求的處理器Handler,這裡便會找到ZuulHandlerMapping
ZuulHandlerMapping是一個HandlerMapping,HandlerMapping稱為處理器對映器,這裡的處理器表示處理http請求,對映器表示根據請求中的一些特徵(一般是url,請求頭等)對映到一個處理器。當一個請求交由DispatcherServlet
處理的時候,會首先透過處理器對映器找到合適的處理器
1.DispatcherServlet
根據請求找到合適的處理器
DispatcherServlet
會遍歷spring容器中的所有HandlerMapping的實現呼叫其getHandler方法找到處理器,然後和HandlerInterceptor
一起包裝成HandlerExecutionChain
(後續會先呼叫HandlerInterceptor
的preHandle前置處理,postHandle後置處理,請求處理完畢後呼叫afterCompletion)
2.ZuulHandlerMapping 發光發熱
當一個請求呼叫到我們的zuul閘道器的時候,DispatcherServlet
根據請求找到合適的處理器的這一步,會呼叫到zuul自動配置注入到spring容器中的ZuulHandlerMapping
,那麼ZuulHandlerMapping 做了寫什麼呢
2.1利用RouteLocator
註冊Handler
在ZuulHandlerMapping中,根據請求找合適的處理器最終交給lookupHandler
方法,此方法呼叫registerHandlers
註冊請求處理器
這裡出現一個新的類RouteLocator
路由定位器,顧名思義就是用來獲取路由的方法
這裡的路由定位器是CompositeRouteLocator,其內部使用一個集合儲存其他的RouteLocator,一般zuul自動配置會為我們注入一個DiscoveryClientRouteLocator(父類是SimpleRouteLocator,會讀取配置檔案中的路由配置)其基於服務發現(DiscoveryClient#getServices
方法)並且將微服務的serviceId如A註冊成路由/A/**
,意味著A開頭的請求後續都將路由到微服務A
最終會將這些路由的url註冊到一個map當中,map的value是ZuulController
2.2.路徑匹配獲取請求處理器ZuulController
ZuulHandlerMapping是一個AbstractUrlHandlerMapping,顧名思義會根據url找對應的請求處理器,邏輯如下
這裡找到的請求處理器,必然是ZuulController(如果路徑和配置,或者服務發現中serviceId匹配上的話)
二丶ZuulController處理請求
1.將zuulController組裝成HandlerExecutionChain
HandlerExecutionChain內部使用陣列儲存攔截器HandlerInterceptor
,在ZuulHandlerMapping找到對應的ZuulController(其實是一個單例物件,都是同一個),會從容器中拿到HandlerInterceptor
型別的bean,和ZuulController包裝到一起生成HandlerExecutionChain
2.獲取ZuulController匹配的HandlerAdapter
想呼叫ZuulController?還需要其對應的HandlerAdapter,由於SpringMVC定義了多種處理器(比如RequestMappingHandlerAdapter(我們最常用的,基於@RequestMapping註解,反射呼叫Controller方法的處理器對應的介面卡)SimpleServletHandlerAdapter(適配Servlet),SimpleControllerHandlerAdapter(適配Controller介面物件)),為了遮蔽這種差異springmvc又定義了一個HandlerAdapter——處理器介面卡,使用介面卡模式,如同一個多功能轉接頭來適配多種處理器
找到合適處理器介面卡的程式碼如下
這裡自然找到的是SimpleControllerHandlerAdapter
隨後會執行攔截器HandlerInterceptor#preHandle
方法
3.介面卡呼叫ZuulController處理請求
在攔截器的preHandler執行完成後,接下來會呼叫ZuulController#handleRequest
,這一步是使用SimpleControllerHandlerAdapter
進行的方法呼叫,我們看下ZuulController是如何處理請求的
ZuulController
是一個ServletWrappingController
(內部包裝servlet),對請求的處理最終交給ZuulServlet
兜兜轉轉最終還是來到了ZuulServlet,其本質是一個Servlet物件,看來Zuul處理Spring框架下可以用,普通的Servlet應用也可以使用呀
三丶ZuulServlet處理請求
1.ZuulServlet初始化ZuulRunner
在zuulServlet真正處理器請求之前,會初始化一個zuulRunner,ZuulRunner的init方法會被呼叫,zuul在這裡面初始化了一個基於ThreadLocal的請求上下文,ZuulRunner提供了preRoute
,postRoute
,route
,error
方法,顧名思義分別在路由前,路由後,路由,以及發生錯誤的時候進行呼叫
2.ZuulServlet處理請求
可以看到ZuulServlet存在三個階段,preRoute,route,postRoute,如果出現異常那麼將呼叫errorRoute
這些方法的呼叫都委託給ZuulRunner進行,ZuulRunner會呼叫FilterProcessor.getInstance()
對應的方法
最終是FilterLoader.getInstance()
負責找到合適的Filter並且排序得到先後順序之後依次呼叫。
具體存在哪些Filter做了什麼事情,我們在ZuulFilter章節細說
3.ZuulFilter
ZuulFilter實現了Comparable 和 IZuulFilter
-
實現Comparable ,並且定義了方法filterOrder,在比較方法中,起始是呼叫filterOrder得到順序然後進行比較
-
IZuulFilter 是Zuul定義的過濾器介面(注意不是Servlet規範中的過濾器)
但是據我看到的原始碼,
FilterLoader
拿到的過濾器都是實現了ZuulFilter的
3.1 preRoute
上面原始碼我們說到了,對過濾器的呼叫,最終是委託給FilterLoader,在FilterRegistry中找到對應型別的過濾器,並且進行排序,然後依次呼叫。下面我們看下pre
型別的過濾器有哪些,都做了什麼事情
3.1.1 ServletDetectionFilter
這個過濾器,負責呼叫ServletRequest#getAttribute
方法判斷,請求是否透過DispatcherServlet處理,然後來到ZuulController,接著呼叫ZuulServlet來到此,判斷的依據就是經過DispatcherServlet處理的請求,會被呼叫DispatcherServlet#setAttribute(常量字串,WebApplicationContext web環境下spring上下文)
3.1.2 Servlet30WrapperFilter
負責將請求適配成Servlet3.0所規範的樣子
使用Servlet30RequestWrapper
包裝原有的請求來實現,有點介面卡,又有點裝飾器設計模式的意思
3.1.3 FormBodyWrapperFilter
如果請求的content-type
中帶有application/x-www-form-urlencoded
或multipart/form-data
- application/x-www-form-urlencoded:表示資料傳送到伺服器之前,所有字元都會進行編碼。屬於比較常用的編碼方式。
- multipart/form-data:指表單資料有多部分構成,既有文字資料,又有檔案等二進位制資料的意思。
那麼FormBodyWrapperFilter 會生效,將對原本請求使用FormBodyRequestWrapper
進行包裝,並提取原始請求的contentData、contentLength、contentType賦值到FormBodyRequestWrapper
的屬性上
3.1.4 DebugFilter
如果請求中zuul.debug.parameter
引數對應的值是true 那麼,將呼叫RequestContext#setDebugRouting(true)
和RequestContext#setDebugRequest(true)
這兩個設定的作用應該是讓zuul列印出一些幫助除錯的日誌資訊
3.1.5 PreDecorationFilter
根據提供的RouteLocator
確定路由的位置和方式。還為下游請求設定各種與代理相關的標頭。此類執行邏輯分支很多
3.2 route
route型別閘道器負責,將請求路由到對應的服務,其中最為關鍵的便是RibbonRoutingFilter
,它基於Ribbon負載均衡
3.2.1 RibbonRoutingFilter
基於Ribbon負載均衡,實現服務呼叫的過濾器。
-
構建RibbonCommandContext,就是對下游微服務的請求的上下文資料的封裝,會記錄目標服務的id,請求方式,請求資源uri,請求頭,請求引數,請求體等等(配置中透過sensitiveHeaders指定哪些請求頭不透傳)
-
forward是根據serviceId,和微服務中註冊的應用,依據負載均衡的策略,挑選一個健康的例項傳送請求
- RibbonCommandFactory#Create
其中
FallbackProvider
在呼叫失敗後,將呼叫fallbackResponse
來進行服務降級RibbonLoadBalancingHttpClient
使用ribbon實現負載均衡的Http客戶端- RibbonCommand#execute
其會根據ILoadBalance呼叫到IRule(ribbon負載均衡策略)選擇一個健康的例項,然後使用HttpClient傳送請求,包裝結果然後返回
-
setResponse就是在上下文中記錄下,forward呼叫得到的請求結果。
3.2.2 SendForwardFilter
使用RequestDispatcher轉發請求的Route ZuulFilter。一般是在配置檔案中配置 location:forward.to
或者配置url:forward:xxxx
的時候會生效。會使用RequestDispatcher#forward
來進行轉發
3.3 post
3.3.1 SendResponseFilter
負責將路由請求結果寫入到HttpServletResponse
中
- 寫請求頭 呼叫
HttpServletResponse#addHeader
將請求頭加到HttpServletResponse
物件中 - 寫請求內容,就是使用流複製route階段的請求結果到
HttpServletResponse
中
3.4 error
3.4.1 SendErrorFilter
如果前面階段的過濾器出現錯誤,將呼叫此過濾器,它負責使用RequestDispatcher
將請求路由到/error
至此我們看完了zuul轉發請求,寫請求內容到`HttpServletResponse`的流程
接下來將回到 DispatchServlet 中
四丶DispatchServlet收尾工作
接下來便是呼叫HandlerInterceptor#postHandle
然後呼叫HandlerInterceptor#afterCompletion
,並推送一個ServletRequestHandledEvent
事件其中記錄了請求資訊和處理時間等等資訊
最後DispatchServlet處理結束,tomcat負責將請求結果返回給呼叫方。
五丶擴充套件自己的ZuulFilter
實現ZuulFilter,並將自己的ZuulFilter註冊到Spring容器中,可以實現一些自定義操作。
ZuulFilter
需要實現filterType(過濾器型別,字串,可以選擇pre route post)
,filterOrder(決定Filter的順序,值越大順序越後)
在公司我見過使用自定義的zuulFilter將Tracer注入到請求中,實現分散式鏈路日誌