SOFAandroid
Scalable Open Financial Architecture 是螞蟻金服自主研發的金融級分佈式中間件,包含了構建金融級雲原生架構所需的各個組件,是在金融場景裏錘鍊出來的最佳實踐。git
SOFATracer 是一個用於分佈式系統調用跟蹤的組件,經過統一的 TraceId 將調用鏈路中的各類網絡調用狀況以日誌的方式記錄下來,以達到透視化網絡調用的目的,這些鏈路數據可用於故障的快速發現,服務治理等。github
本文爲《剖析 | SOFATracer 框架》最後一篇,本篇做者sqyu,來自小象生鮮。《剖析 | SOFATracer 框架》系列由 SOFA 團隊和源碼愛好者們出品,項目代號:<SOFA:TracerLab/> ,目前已經所有完成,可在文末獲取本系列文章目錄,感謝你們的參與。web
SOFATracer:後端
自 Google《Dapper,大規模分佈式系統的跟蹤系統》論文發表以來,開源 Tracer 系統如雨後春筍般相繼面市,各顯神通,但都是用於分佈式系統調用跟蹤的組件,經過統一的 traceId 將調用鏈路中的各類網絡調用狀況記錄下來,以達到透視化網絡調用的目的。本文介紹的 SOFATracer 是以日誌的形式來記錄的,這些日誌可用於故障的快速定位,服務治理等。目前來看 SOFATracer 團隊已經爲咱們搭建了一個完整的 Tracer 框架內核,包括數據模型、編碼器、跨進程透傳 traceId、採樣、日誌落盤與上報等核心機制,並提供了擴展 API 及基於開源組件實現的部分插件,爲咱們基於該框架打造本身的 Tracer 平臺提供了極大便利。bash
做爲一個開源實現,SOFATracer 也儘量提供大而全的插件實現,但因爲多數公司都有本身配套的技術體系,徹底依賴官方提供的插件可能沒法知足自身的須要,所以如何基於 SOFATracer 自身 API 的組件埋點機制進行擴展,實現本身的插件是必須掌握的一項本領。cookie
本文將根據 SOFATracer 自身 API 的擴展點及已提供的插件源碼來分析下 SOFATracer 插件的埋點機制。網絡
對一個應用的跟蹤要關注的無非就是 客戶端->web 層->rpc 服務->dao 後端存儲、cache 緩存、消息隊列 mq 等這些基礎組件。SOFATracer 插件的做用實際上也就是對不一樣組件進行埋點,以便基於這些組件採集應用的鏈路數據。架構
不一樣組件有不一樣的應用場景和擴展點,所以對插件的實現也要因地制宜,SOFATracer 埋點方式通常是經過 Filter、Interceptor 機制實現的。
SOFATracer 目前已實現的插件中,像 SpringMVC 插件是基於 Filter 進行埋點的,httpclient、resttemplate 等是基於 Interceptor 機制進行埋點的。在實現插件時,要根據不一樣插件的特性和擴展點來選擇具體的埋點方式。正所謂條條大路通羅馬,無論怎麼實現埋點,都是依賴 SOFATracer 自身 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 RPC 插件實現爲例,分析如何實現一個埋點插件。
一、實例工程的分包結構
從上圖插件的工程的包結構能夠看出,整個插件實現比較簡單,代碼量很少,但從類的定義來看,直觀的體現了SOFATracer 插件埋點機制所介紹的套路。下面將進行詳細的分析與介紹。
二、實現 Tracer 實例
RpcThriftTracer 繼承了 AbstractTracer 類,是對 clientTracer、serverTracer 的擴展。
PS:如何肯定一個組件是 client 端仍是 server 端呢?就是看當前組件是請求的發起方仍是請求的接受方,若是是請求發起方則通常是 client 端,若是是請求接收方則是 server 端。那麼對於 RPC 來講,便是請求的發起方也是請求的接受方,所以這裏實現了 AbstractTracer 類。
三、擴展點類實現
PS:上面表格中 SpanEncoder 和 AbstractSofaTracerStatisticReporter 的實現中,多了一層AbstractRpcDigestSpanJsonEncoder 和AbstractRpcStatJsonReporter的抽象,主要是因爲 client 和 server 端有公共的邏輯處理,爲了減小冗餘代碼,而採用了多繼承模式處理。
四、數據傳播格式實現
SOFATracer 支持使用 OpenTracing 的內建格式進行上下文傳播。
五、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 源碼來逐一分析下這幾個過程。
Thrift 插件中的 Consumer 和 Provider 分別對應於 client 和 server 端存在的,因此在 client 端就是將當前請求線程的產生的 traceId 相關信息 Inject 到 SpanContext,server 端從請求中 extract 出 spanContext,來還本來次請求線程的上下文。
相關處理邏輯在FilterThriftBase抽象類中,以下圖:
inject 實現代碼
extract 實現代碼
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、當前請求的請求方法以及請求大小等。
在 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 的插件:
一、肯定所要實現的插件,理解該組件的使用場景和擴展點,而後肯定以哪一種方式來埋點,好比:是 Filter or Interceptor
二、實現當前插件的 Tracer 實例,這裏需明確當前插件是以 client 存在仍是以 server 存在
三、實現一個枚舉類,用來描述當前組件的日誌名稱和滾動策略 key 值等
四、實現插件摘要日誌的 Encoder ,實現當前組件的定製化輸出
五、實現插件的統計日誌 Reporter 實現類,經過繼承 AbstractSofaTracerStatisticReporter 類並重寫 doReportStat
六、定義當前插件的傳播格式
七、要明確咱們須要收集哪些數據
本文經過對 SOFATracer 插件的埋點機制進行分析介紹,並結合自定義 Thrift RPC 插件的埋點實現進行了分析。但願經過本文可以讓更多的同窗理解基於 SOFATracer 自身 API 的埋點實現,能根據自身須要實現本身的插件。
《Dapper,大規模分佈式系統的跟蹤系統》:
歡迎加入互動釘釘羣,搜索羣號:23127468 便可加入。
長按關注,獲取分佈式架構乾貨
歡迎你們共同打造 SOFAStack https://github.com/alipay