SOFA
Scalable Open Financial Architecture 是螞蟻金服自主研發的金融級分散式中介軟體,包含了構建金融級雲原生架構所需的各個元件,是在金融場景裡錘煉出來的最佳實踐。
SOFATracer 是一個用於分散式系統呼叫跟蹤的元件,透過統一的 TraceId 將呼叫鏈路中的各種網路呼叫情況以日誌的方式記錄下來,以達到透視化網路呼叫的目的,這些鏈路資料可用於故障的快速發現,服務治理等。
本文為《剖析 | SOFATracer 框架》最後一篇,本篇作者sqyu,來自小象生鮮。《剖析 | SOFATracer 框架》系列由 SOFA 團隊和原始碼愛好者們出品,專案代號: ,目前已經全部完成,可在文末獲取本系列文章目錄,感謝大家的參與。
SOFATracer:
https://github.com/alipay/sofa-tracer
前言
自 Google《Dapper,大規模分散式系統的跟蹤系統》論文發表以來,開源 Tracer 系統如雨後春筍般相繼面市,各顯神通,但都是用於分散式系統呼叫跟蹤的元件,透過統一的 traceId 將呼叫鏈路中的各種網路呼叫情況記錄下來,以達到透視化網路呼叫的目的。本文介紹的 SOFATracer 是以日誌的形式來記錄的,這些日誌可用於故障的快速定位,服務治理等。目前來看 SOFATracer 團隊已經為我們搭建了一個完整的 Tracer 框架核心,包括資料模型、編碼器、跨行程透傳 traceId、取樣、日誌落盤與上報等核心機制,並提供了擴充套件 API 及基於開源元件實現的部分外掛,為我們基於該框架打造自己的 Tracer 平臺提供了極大便利。
作為一個開源實現,SOFATracer 也盡可能提供大而全的外掛實現,但由於多數公司都有自己配套的技術體系,完全依賴官方提供的外掛可能無法滿足自身的需要,因此如何基於 SOFATracer 自身 API 的元件埋點機制進行擴充套件,實現自己的外掛是必須掌握的一項本領。
本文將根據 SOFATracer 自身 API 的擴充套件點及已提供的外掛原始碼來分析下 SOFATracer 外掛的埋點機制。
SOFATracer 的外掛埋點機制
對一個應用的跟蹤要關註的無非就是 客戶端->web 層->rpc 服務->dao 後端儲存、cache 快取、訊息佇列 mq 等這些基礎元件。SOFATracer 外掛的作用實際上也就是對不同元件進行埋點,以便基於這些元件採集應用的鏈路資料。
不同元件有不同的應用場景和擴充套件點,因此對外掛的實現也要因地制宜,SOFATracer 埋點方式一般是透過 Filter、Interceptor 機制實現的。
元件擴充套件入口之 Filter or Interceptor
SOFATracer 目前已實現的外掛中,像 SpringMVC 外掛是基於 Filter 進行埋點的,httpclient、resttemplate 等是基於 Interceptor 機制進行埋點的。在實現外掛時,要根據不同外掛的特性和擴充套件點來選擇具體的埋點方式。正所謂條條大路通羅馬,不管怎麼實現埋點,都是依賴 SOFATracer 自身 API 的擴充套件機制來實現。
API 擴充套件點之 AbstractTracer API
SOFATracer 中所有的外掛均需要實現自己的 Tracer 實體,如 SpringMVC 的 SpringMvcTracer 、HttpClient 的 HttpClientTracer 等。
-
基於 SOFATracer API 埋點方式外掛擴充套件如下:
AbstractTracer 是 SOFATracer 用於外掛擴充套件使用的一個抽象類,根據外掛型別不同,又可以分為 clientTracer 和 serverTracer,分別對應於 AbstractClientTracer 和 AbstractServerTracer;再透過 AbstractClientTracer 和 AbstractServerTracer 衍生出具體的元件 Tracer 實現,比如上圖中提到的 HttpClientTracer 、RestTemplateTracer 、SpringMvcTracer 等外掛 Tracer 實現。
AbstractTracer
這裡先來看下 AbstractTracer 這個抽象類中具體提供了哪些抽象方法,也就是對於 AbstractClientTracer 和 AbstractServerTracer 需要分別擴充套件哪些能力。
從上圖 AbstractTracer 類提供的抽象方法來看,不管是 client 還是 server,在具體的 Tracer 外掛實現中,都必須提供以下實現:
-
DigestReporterLogName :當前元件摘要日誌的日誌名稱
-
DigestReporterRollingKey : 當前元件摘要日誌的滾動策略
-
SpanEncoder:對摘要日誌進行編碼的編碼器實現
-
AbstractSofaTracerStatisticReporter : 統計日誌 reporter 類的實現類
基於 SOFATracer 自身 API 埋點最大的優勢在於可以透過上面的這些引數來實現不同元件日誌之間的隔離,上述需要實現的這些點是實現一個元件埋點常規的擴充套件點,是不可缺少的。
上面分析了 SOFATracer API 的埋點機制,並且對於一些需要擴充套件的核心點進行了說明。SOFATracer 自身提供的核心非常簡單,其基於自身 API 的埋點擴充套件機製為外部使用者定製元件埋點提供了極大的便利。下麵以 Thrift 擴充套件,具體分析如何實現一個元件埋點。
PS : Thrift 是外部使用者基於 SOFATracer API 擴充套件實現的,目前僅用於其公司內部使用,SOFATracer 官方元件中暫不支援,請知悉;後續會溝通作者提供 PR ,在此先表示感謝。
Thrift 外掛埋點分析
這裡我們以 Thrift RPC 外掛實現為例,分析如何實現一個埋點外掛。
-
1、實體工程的分包結構
從上圖外掛的工程的包結構可以看出,整個外掛實現比較簡單,程式碼量不多,但從類的定義來看,直觀的體現了SOFATracer 外掛埋點機制所介紹的套路。下麵將進行詳細的分析與介紹。
-
2、實現 Tracer 實體
RpcThriftTracer 繼承了 AbstractTracer 類,是對 clientTracer、serverTracer 的擴充套件。
PS:如何確定一個元件是 client 端還是 server 端呢?就是看當前元件是請求的發起方還是請求的接受方,如果是請求發起方則一般是 client 端,如果是請求接收方則是 server 端。那麼對於 RPC 來說,即是請求的發起方也是請求的接受方,因此這裡實現了 AbstractTracer 類。
-
3、擴充套件點類實現
PS:上面表格中 SpanEncoder 和 AbstractSofaTracerStatisticReporter 的實現中,多了一層AbstractRpcDigestSpanJsonEncoder 和AbstractRpcStatJsonReporter的抽象,主要是由於 client 和 server 端有公共的邏輯處理,為了減少冗餘程式碼,而採用了多繼承樣式處理。
-
4、資料傳播格式實現
SOFATracer 支援使用 OpenTracing 的內建格式進行背景關係傳播。
-
5、Thrift Rpc 自身擴充套件點之請求攔截埋點
我們內部 Thrift 支援 SPI Filter 機制,因此要實現對請求的攔截過濾,示例外掛埋點的實現就是基於 SPI Filter 機制完成的。其中 FilterThriftBase 抽象也是為了便於處理 consumerFilter 和 providerFilter 公共的邏輯抽象。
外掛擴充套件基本思路總結
對於一個元件來說,一次處理過程一般是產生一個 Span;這個 Span 的生命週期是從接收到請求到傳迴響應這段過程。
但是這裡需要考慮的問題是如何與上下游鏈路關聯起來呢?在 Opentracing 規範中,可以在 Tracer 中 extract 出一個跨行程傳遞的 SpanContext 。然後透過這個 SpanContext 所攜帶的資訊將當前節點關聯到整個 Tracer 鏈路中去,當然有提取(extract)就會有對應的註入(inject);更多請參考 螞蟻金服分散式鏈路跟蹤元件鏈路透傳原理與SLF4J MDC的擴充套件能力分析 | 剖析 。
鏈路的構建一般是 client-server-client-server 這種樣式的,那這裡就很清楚了,就是會在 client 端進行註入(inject),然後再 server 端進行提取(extract),反覆進行,然後一直傳遞下去。
在拿到 SpanContext 之後,此時當前的 Span 就可以關聯到這條鏈路中了,那麼剩餘的事情就是收集當前元件的一些資料;整個過程大概分為以下幾個階段:
-
從請求中提取 spanContext
-
構建 Span,並將當前 Span 存入當前 tracer背景關係中(SofaTraceContext.push(Span))
-
設定一些資訊到 Span 中
-
傳迴響應
-
Span 結束&上報
下麵結合 SOFATracer 自身 API 原始碼來逐一分析下這幾個過程。
從請求中提取 spanContext
Thrift 外掛中的 Consumer 和 Provider 分別對應於 client 和 server 端存在的,所以在 client 端就是將當前請求執行緒的產生的 traceId 相關資訊 Inject 到 SpanContext,server 端從請求中 extract 出 spanContext,來還原本次請求執行緒的背景關係。
相關處理邏輯在FilterThriftBase抽象類中,如下圖:
-
inject 實現程式碼
-
extract 實現程式碼
獲取 Span & 資料獲取
serverReceive 這個方法是在 AbstractTracer 類中提供了實現,子類不需要關註這個。在 SOFATracer 中也是將請求大致分為以下幾個過程:
-
客戶端傳送請求 clientSend cs
-
服務端接受請求 serverReceive sr
-
服務端傳回結果 serverSend ss
-
客戶端接受結果 clientReceive cr
無論是哪個外掛,在請求處理週期內都可以從上述幾個階段中找到對應的處理方法。因此,SOFATracer 對這幾個階段處理進行了封裝。見下圖:
這四個階段實際上會產生兩個 Span,第一個 Span 的起點是 cs,到 cr 結束;第二個 Span 是從 sr 開始,到 ss 結束。
-
clientSend serverReceive … serverSend clientReceive
來看下 Thrift Rpc 外掛中 Consumer 和 Provider 的實現
-
ConsumerTracerFilter
紅色框內對應的客戶端傳送請求,也就是 cs 階段,會產生一個 Span。
-
ProviderTracerFilter
服務端接收請求 sr 階段,產生了一個 Span 。上面appendProviderRequestSpanTags這段程式碼是為當前這個 Span 設定一些基本資訊,包括當前應用的應用名、當前請求的 service、當前請求的請求方法以及請求大小等。
傳迴響應與結束 Span
在 Filter 鏈執行結束之後,ConsumerTracerFilter(見圖一)和 ProviderTracerFilter(見圖二) 分別在 finally 塊中又補充了當前請求響應結果的一些資訊到 Span 中去。然後分別呼叫 clientReceive 和 serverSend 結束當前 Span。
-
圖一
-
圖二
關於 clientReceive 和 serverSend 裡面呼叫 Span.finish 這個方法( opentracing 規範中,Span.finish 的執行標志著一個 Span 的結束(見圖一),當呼叫finish執行邏輯時同時會進行span資料的上報(見圖二)和當前請求執行緒MDC資源的清理操作(見圖三)等。
-
圖一:
當前 Span 資料上報,程式碼如下:
-
圖二:
清理當前請求執行緒的 MDC 資源的一些邏輯處理等,程式碼如下:
-
圖三:
外掛編寫流程總結
上述以自定義 Thrift RPC 外掛為例,分析了下 SOFATracer 外掛埋點實現的一些細節。前面不僅總結了編寫外掛的基本埋點思路而且還對 SOFATracer 自身 API 實現做了相應的分析。基於此本節則從整體思路上來總結如何編寫一個 SOFATracer 的外掛:
-
1、確定所要實現的外掛,理解該元件的使用場景和擴充套件點,然後確定以哪種方式來埋點,比如:是 Filter or Interceptor
-
2、實現當前外掛的 Tracer 實體,這裡需明確當前外掛是以 client 存在還是以 server 存在
-
3、實現一個列舉類,用來描述當前元件的日誌名稱和滾動策略 key 值等
-
4、實現外掛摘要日誌的 Encoder ,實現當前元件的定製化輸出
-
5、實現外掛的統計日誌 Reporter 實現類,透過繼承 AbstractSofaTracerStatisticReporter 類並重寫 doReportStat
-
6、定義當前外掛的傳播格式
-
7、要明確我們需要收集哪些資料
小結
本文透過對 SOFATracer 外掛的埋點機制進行分析介紹,並結合自定義 Thrift RPC 外掛的埋點實現進行了分析。希望透過本文能夠讓更多的同學理解基於 SOFATracer 自身 API 的埋點實現,能根據自身需要實現自己的外掛。
文中涉及到的連結:
-
《Dapper,大規模分散式系統的跟蹤系統》:
https://bigbully.github.io/Dapper-translation/
【螞蟻金服分散式鏈路跟蹤元件 SOFATracer | 剖析】系列
長按關註,獲取分散式架構乾貨
歡迎大家共同打造 SOFAStack https://github.com/alipay