調用鏈系列(1):解讀UAVStack中的貪吃蛇

  • 2019 年 10 月 3 日
  • 筆記

一、背景

對於分散式在線服務,一個請求需要經過多個系統中多個模組,可能多達上百台機器的協作才能完成單次請求。這種場景下單靠人力無法掌握整個請求中各個階段的性能開銷,更無法快速的定位系統中性能瓶頸。當發生故障時通常需要查看大量日誌跨越多個團隊來確認問題。

二、舉個栗子

程式猿小亮作為一個在職場摸爬滾打多年資深工程師,他可能面臨的系統設計是這個樣子的,如下圖。

(圖片來自於網路)

藉助良好的系統設計和編碼規範,對於一般有問題的請求處理,小亮依據自己對多個系統的了解通過翻閱大量的日誌文件(前提是日誌輸出也需要規範)花費兩個小時來定位到問題。隨著用戶的不斷增長系統複雜度也呈現指數增長,小亮的大部分時間都浪費在了團隊溝通之類的工作上。小亮的幸福指數也像系統複雜度一樣呈現指數下降。

小亮這時可能會想,要是有一個東西能把每次請求經過的系統都記錄下來,要是能把每個節點消耗時間、處理類神馬的資訊也抓出來那這個世界得多麼美好。

一個偶然的機會小亮知道了UAVStack其中一個叫做調用鏈的神奇功能,在對業務程式碼沒有任何侵入的前提下輕鬆解決了他的難題。下面就讓我們一起來開啟一段探索UAVStack的神奇之旅。

UAVStack調用鏈技術棧支援

三、效果展示

輕型調用鏈展示詳情:

重調用鏈開啟以後請求報文體抓取視圖:

更多使用技巧和說明請參閱官網:https://uavorg.github.io/documents/uavdoc_useroperation/91.html(用戶指南中調用鏈部分)。

四、具體實現

UAVStack調用鏈實現分為模型設計、服務端資訊收集(輕/重)、方法級資訊收集(輕/重)、客戶端資訊收集(輕/重)、調用鏈協議設計(輕/重)、調用鏈上下文傳遞、調用資訊記錄及傳遞、調用數據統計處理等。由於篇幅限制,本期暫時只分享其中的模型設計及實現調用鏈模型時序圖。

五、模型設計

借鑒前人經驗並揉合具體業務場景需求,抽象出了如下調用鏈模型:

調用鏈元數據:

1)SpanEndpointType:調用類型(Root(“E”),Service(“S”), Client(“C”), Method(“M”));

Root指本條調用鏈中的第一個節點,即一條調用鏈的開始位置,可以是一個服務請求,一次httpclient調用等;

Service指當前調用鏈中非第一個節點且是系統中對外提供的服務,如用戶登錄服務;

Client指當前調用鏈中非第一個節點且是當前系統與外部溝通的一種途徑,如httpclient、mongoclient等;

Method值當前調用鏈中非第一個節點且是系統中的一個函數,如日誌數出函數等。

2)traceId:調用鏈唯一標識符;

3)spanId:一條調用鏈中當前節點的調用順序(與SpanEndpointType 結合唯一);spanId採用分層設計,形如1.2.1,既能表示調用順序同時又能反應所才調用鏈層級;

4)parentId:一條調用鏈中當前節點的父調用節點。

調用鏈繪製規則:

1)調用者(服務、web)最初調用(無父調用)記為開始節點E,並生成唯一調用鏈ID,traceID;

2)系統內應用組件調用(如httpclient,方法調用等),spanId末尾數字加1(若為第一個則末尾加.1);

3)系統間調用(如A服務調用B服務),A服務與B服務span資訊只有SpanEndpointType不同(分別對應span的兩個端)。

舉個栗子

背景介紹:用戶小明想通過網路獲取一些知識,通過網路他進入了系統O。服務O中部署了服務A和B,A服務使用httpclient與B通訊,B服務先會與redis交互然後和myql交互,最後系統O將小明感興趣的內容返回給小明;

完成此次請求UAV抽象出如下調用鏈模型:

1)小明(下圖中的調用方)通過門戶訪問了A服務,此時調用鏈生成唯一traceId並將當前節點的SpanEndpointType置為N(第一個節點的意思),spanId置為1(當前調用層中的第一個節點),parentId置為E(沒有父節點的意思);

2)A服務通過httpclient向B服務發起一次http請求,此時調用鏈元數據如下traceId(沿用父節點id);1.1(spanId末尾加.1,因為為第一次調用);1(parentId父節點的spanId);C(調用類型記錄為C客戶端調用);

3)B服務接收到來自於A服務通過httpclient的調用,此時調用鏈元數據如下traceId(沿用初始調用時id);1.1(spanId沿用傳遞過來的spanId);1(parentId沿用傳遞過來的parentId);S(調用類型記錄為S服務端處理請求);

4)B服務先查詢redis,此時調用鏈元數據如下traceId(沿用初始調用時id);1.1.1(spanId末尾加.1,因為為第一次調用);1.1(parentId父節點的spanId);C(調用類型記錄為C客戶端調用);

5)B服務又發起對mysql的查詢,此時調用鏈元數據如下traceId(沿用初始調用時id);1.1.2(spanId末尾數字加1,因為為非第一次調用);1.1(parentId父節點的spanId);C(調用類型記錄為C客戶端調用);

6)處理結束,調用鏈將記錄的資訊進行記錄。

調用鏈時序圖

UAVServer:中間件增強框架,提供在中間件的不同生命周期進行劫持的能力,即中間件劫持技術,如tomcat webcontainer啟動開始時刻等;

JEEServiceRunGlobalFilterHandler:藉助中間件劫持技術延伸出的全局filter,能夠攔截所有經過中間件(tomcat等)的請求;

ServiceSpanInvokeChainHandler:調用鏈中專註處理歸為Service類型節點的handler;

ClientSpanInvokeChainHandler:調用鏈中專註處理歸為Client類型節點的handler;

XXAdapter:泛指調用鏈中所有的adapter,提供在handler(分為Service、Client、Method三種handler,圖中省略了Method類型)執行動作before和after時刻操作數據的能力。

實現對用戶程式碼無任何”侵入”的前提下完成調用鏈的生成,過程大致分為如下幾個過程:

1)在JEEServiceRunGlobalFilterHandler的doRepuest中包裝解析請求;

2)xxAdapter中的before對數據進行適配;

3)xxHandler處理對應範圍內(Service、Client和Method)內請求數據;

4)xxAdapter中的after對數據進行整理或記錄;

5)在JEEServiceRunGlobalFilterHandler的doResponse中返回處理過後請求。

六、總結

本文主要目的是讓讀者對UAVStack的調用鏈有一個整體的認識,初步了解一條調用鏈繪製的大致生命周期,具體的實現將在以後的分享中詳細介紹。

作者:李崇