在上週的推送中,我們介紹了呼叫鏈的模型設計與模型時序圖,本週將為大家繼續介紹呼叫鏈是如何在中介軟體增強技術的賦能之下實現服務端資訊收集以及服務間上下文傳遞的。
服務端資訊收集
服務端資訊收集流程如下圖所示,通過在應用容器(Tomcat等)啟動過程中植入切點,實現在應用邏輯執行之前和之後對請求進行劫持。
-
應用邏輯執行之前:解析request中的呼叫鏈資訊,並初始化呼叫鏈上下文;
-
應用邏輯執行之後:解析response中的呼叫鏈資訊,並將本次請求處理的所有呼叫鏈資訊輸出到日誌檔案。
切點植入
在介紹切點之前,我們應該整體瞭解Servlet容器(本文以Tomcat為例)處理一次請求的基本流程。
Connector接收到一次連線並轉化成請求(Request)後,會將請求傳遞到Engine管道(Pipeline)的閥(ValveA)中。請求通過Engine的管道傳遞到Engine Valve,然後經由Engine Valve傳遞到一個Host的管道中,並在該管道中傳遞到Host Valve這個閥裡;接著從Host Valve傳遞到一個Context的管道中,並在該管道中傳遞到Context Valve中;最後傳遞到Wrapper C內管道所包含的閥Wrapper Valve中,在這裡經過過濾器鏈(Filter Chain)最終送到一個Servlet中。根據Tomcat的這種架構設計特點,我們可以在Tomcat處理一次請求的生命週期過程中植入自己的邏輯,從而增強Tomcat對外提供的能力,即UAV的中介軟體增強技術。
中介軟體增強技術除了巧妙運用了tomcat容器的架構設計之外還藉助了java Instrumentation(它給我們提供了一種能夠在物件第一次載入時動態修改位元組碼的能力,由於篇幅原因在此不進行詳細講解)。在UAV中通過UAVServer對外提供各種切點能力。
有了中介軟體增強技術,就有了在應用邏輯執行之前和之後的切點,接下來就是在這些切點位置執行我們自己的呼叫鏈邏輯了。
中介軟體增強技術在呼叫鏈中的使用
上文介紹的間件增強技術是一種通過使用javaagent方式動態地在Tomcat程式碼中植入切點程式碼並以UAVServer的形式對外提供能力的框架(具體能力後續文章會詳細介紹)。輕呼叫鏈正是依賴於UAVServer對外提供的GlobalFilterHandler能力。 GlobalFilterHandler:這裡的GlobalFilterHandler是中介軟體增強技術中的一種能力,與傳統的filter沒有任何關係。它對外提供了四種能力:
- doRequest:在所有應用處理請求之前進行劫持;
- doResponse:在所有應用處理請求之後進行劫持;
- BlockHandlerChain:阻塞自當前handler以後的所有handler,此處的handler為註冊在當前;
- BlockFilterChain阻塞自當前Filter以後的所有Filter。 呼叫鏈藉助GlobalFilterHandler提供的前兩個能力,實現了在應用處理請求之前和之後執行呼叫鏈邏輯的功能。
輕呼叫鏈實現
UML圖如下:
從UML圖中可以清晰地看到,InvokeChainSupporter(呼叫鏈實現邏輯入口和呼叫鏈所需資源初始化實現類)將中介軟體增強技術進行了二次增強。它允許使用者在其中註冊不同的handler,並且在handler的preCap和doCap(中介軟體增強技術中的邏輯執行之前和之後的切點術語)方法之前和之後動態植入adapter,從而執行更多的定製化適配和個性化邏輯。所有supporter和adapter均採用反射呼叫方式,最大程度上減少了中介軟體增強技術的依賴。
有了二次增強技術,我們就可以開始下面的呼叫鏈繪製工作了。
輕呼叫鏈繪製實現主要依賴於註冊在InvokeChainSupporter上的ServiceSpanInvokeChainHandler。主要繪製過程如下:
- 解析請求資訊,從中提取呼叫鏈關心的資訊,並將解析出來的資訊放入上下文中;
- 對解析出來的請求頭資訊進行邏輯分流,根據不同的協議型別進行不同的邏輯處理(MQ邏輯、Http邏輯、Dubbo邏輯);
- 初始化呼叫鏈上下文,並初始化main span上下文;
- 在應用處理完請求之後,統一輸出呼叫鏈資訊。
下面來看一下具體每一步都做了什麼。
解析請求資訊
對於像Tomcat這樣的中介軟體容器,進入Tomcat的所有請求都會被封裝成HttpServletRequest和HttpServletResponse(後文簡稱request和response)最終進入使用者的Servlet中。藉助中介軟體增強技術,呼叫鏈在使用者邏輯處理之前將request和response進行一次攔截,並解析其中是否含有呼叫鏈資訊。如果有,則將呼叫鏈資訊進行封裝放入上下文中。
邏輯分流
由於不同協議對應的呼叫鏈繪製邏輯也不同,此處呼叫鏈會根據協議型別進行一次分發。
初始化呼叫鏈上下文
解析呼叫鏈上下文中的資訊:
- 若沒有父節點則以當前節點為初始化節點,並初始化記錄當前服務內呼叫鏈資訊的main span;
- 若有父節點則根據父節點資訊初始化當前節點,並初始化記錄當前服務內呼叫鏈資訊的main span。 main span:在服務內可能會進行多次客戶端通訊或服務間通訊,需要一個mainspan來記錄當前服務內呼叫鏈最後一個節點的資訊。
呼叫鏈資訊輸出
在使用者邏輯處理結束之後,呼叫鏈記錄器會從上下文中取出當前服務的呼叫鏈資訊並將其輸出到指定日誌路徑。
服務間上下文傳遞
針對不同協議,呼叫鏈傳遞資訊的方式也略有不同,具體實現方式藉助了中介軟體增強技術提供的另一個能力:AppFrkHook(簡稱hook,此功能在客戶端呼叫鏈實現時具體介紹)。Hook能夠對使用者使用的客戶端技術進行劫持,如使用者使用了Httpclient進行通訊,則對Httpclient進行劫持並動態植入程式碼,從而實現在Http通訊的過程中注入呼叫鏈上下文資訊。目標服務在解析請求資訊時,對呼叫鏈上下文進行解析;在初始化呼叫鏈上下文邏輯時,使用傳遞過來的資訊初始化目標服務的呼叫鏈上下文,實現跨系統呼叫時的呼叫鏈連線。
讀完本文,大家應該瞭解了服務端和服務間呼叫鏈的繪製全過程;對中介軟體增強技術的實現及其提供的GlobalFilterHandler能力有了初步認識。下篇文章我們將向大家介紹如何從Http(HyperTextTransferProtocol) 即超文字傳輸協議中獲取request和response的body和header。
UAVStack已在Github上開放原始碼,並提供了安裝部署、架構說明和使用者指南等雙語文件,歡迎訪問-給星-拉取~~~
掃一掃下方二維碼 關注一個不會讓你失望的公眾號