SOFA
Scalable Open Financial Architecture
是螞蟻金服自主研發的金融級分散式中介軟體,包含了構建金融級雲原生架構所需的各個元件,是在金融場景裡錘鍊出來的最佳實踐。SOFATracer 是一個用於分散式系統呼叫跟蹤的元件,通過統一的 TraceId 將呼叫鏈路中的各種網路呼叫情況以日誌的方式記錄下來,以達到透視化網路呼叫的目的,這些鏈路資料可用於故障的快速發現,服務治理等。
本文為《剖析 | SOFATracer 框架》第二篇。《剖析 | SOFATracer 框架》系列由 SOFA 團隊和原始碼愛好者們出品,專案代號:<
SOFA:TracerLab/>
,目前領取已經完成,感謝大家的參與。SOFATracer:
https://github.com/alipay/sofa-tracer
0、前言
在《螞蟻金服分散式鏈路跟蹤元件 SOFATracer 總覽|剖析》一文中已經對 SOFATracer 進行了概要性的介紹。從對 SOFATracer 的定義可以瞭解到,SOFATracer 作為一個分散式系統呼叫跟蹤的元件,是通過統一的 TraceId 將呼叫鏈路中的各種網路呼叫情況以資料上報的方式記錄下來,以達到透視化網路呼叫的目的。
本篇將針對SOFATracer的資料上報方式進行詳細分析,以幫助大家更好的理解 SOFATracer 在資料上報方面的擴充套件。
1、Reporter 整體模型
本節將對 SOFATracer 的 Report 模型進行整體介紹,主要包括兩個部分:1、Reporter 的介面設計及實現;2、資料上報流程。
1.1、Reporter 的介面設計及實現
資料上報是 SofaTracer 基於 OpenTracing Tracer 介面擴充套件實現出來的功能;Reporter 例項作為 SofaTracer 的屬性存在,在構造 SofaTracer 例項時,會初始化 Reporter 例項。
1.1.1、Reporter 介面設計
Reporter 介面是 SOFATracer 中對於資料上報的頂層抽象,核心介面方法定義如下:
//獲取 Reporter 例項型別String getReporterType();
//輸出 spanvoid report(SofaTracerSpan span);
//關閉輸出 span 的能力void close();
複製程式碼
Reporter 介面的設計中除了核心的上報功能外,還提供了獲取 Reporter 型別的能力,這個是因為 SOFATracer 目前提供的埋點機制方案需要依賴這個實現。
1.1.2、Reporter 介面實現
Reporter 的類體系結構如下:
Reporter 的實現類有兩個,SofaTracerCompositeDigestReporterImpl 和 DiskReporterImpl :
- SofaTracerCompositeDigestReporterImpl:組合摘要日誌上報實現,上報時會遍歷當前 SofaTracerCompositeDigestReporterImpl 中所有的 Reporter ,逐一執行 report 操作;可供外部使用者擴充套件使用。
- DiskReporterImpl:資料落磁碟的核心實現類,也是目前 SOFATracer 中預設使用的上報器。
1.2、資料上報流程分析
資料上報實際都是由不同的鏈路元件發起,關於外掛擴充套件機制及埋點方式不是本篇範疇,就不展開了。這裡直接來看資料上報的入口。
在 Opentracing 規範中提到,Span#finish 方法是 span 生命週期的最後一個執行方法,也就意味著一個 span 跨度即將結束。那麼當一個 span 即將結束時,也是當前 span 具有最完整狀態的時候。所以在 SOFATracer 中,資料上報的入口就是 Span#finish 方法,這裡貼一小段程式碼:
//SofaTracerSpan#finish@Overridepublic void finish(long endTime) {
this.setEndTime(endTime);
//關鍵記錄:report span this.sofaTracer.reportSpan(this);
SpanExtensionFactory.logStoppedSpan(this);
}複製程式碼
在 finish 方法中,通過 SofaTracer#reportSpan 將當前 span 進行了上報處理。以這個為入口,整個資料上報的呼叫鏈路如下圖所示:
整個上報呼叫流程其實並不是很難,這裡留兩個問題:
- 如何構造 clientRportor 和 serverReporter 的,依據是什麼?
- 摘要日誌和統計日誌是怎麼落盤的?
第一個問題會在外掛埋點解析篇中給出答案;第二個問題下面來看。
2、日誌落盤
前面已經提到,SOFATracer 本身提供了兩種上報模式,一種是落到磁碟,另外一種是上報到zipkin。在實現細節上,SOFATracer 沒有將這兩種策略分開以提供獨立的功能支援,而是將兩種上報方式組合在了一起,然後再通過配置引數來控制是否進行具體的上報邏輯,具體參考下圖:
本節將來剖析下日誌落盤的實現細節。日誌落盤又分為摘要日誌落盤 和 統計日誌落盤;摘要日誌是每一次呼叫均會落地磁碟的日誌;統計日誌是每隔一定時間間隔進行統計輸出的日誌。
2.1、摘要日誌落盤
摘要日誌落盤是基於 Disruptor 高效能無鎖迴圈佇列實現的。SOFATracer 中,AsyncCommonDigestAppenderManager 類對 disruptor 進行了封裝,用於處理外部元件的 Tracer 摘要日誌列印。
關於 Disruptor 的原理及其自身的事件模型此處不展開分析,有興趣的同學可以自行查閱相關資料。這裡直接看下 SOFATracer 中是如何使用 Disruptor 的。
2.1.1、訊息事件模型
SOFATracer 使用了兩種不同的事件模型,一種是 SOFATracer 內部使用的 StringEvent,一種是外部擴充套件使用的SofaTacerSpanEvent。詳見:SofaTracerSpanEvent &
StringEvent 。
2.1.2、Consumer 消費者
Consumer 是 AsyncCommonDigestAppenderManager 的內部類;實現了 EventHandler 介面,這個 Consumer 作為消費者存在,監聽事件,然後通過 TraceAppender 將 span 資料 flush 到磁碟。詳見:AsyncCommonDigestAppenderManager
2.1.3、Disruptor 的初始化
- Disruptor 的構建:在 AsyncCommonDigestAppenderManager 的建構函式中完成的。
//構建disruptor,使用的是 ProducerType.MULTI//等待策略是 BlockingWaitStrategy,考慮到的是CPU的使用率和一致性disruptor = new Disruptor<
SofaTracerSpanEvent>
(new SofaTracerSpanEventFactory(), realQueueSize, threadFactory, ProducerType.MULTI, new BlockingWaitStrategy());
複製程式碼
- 異常處理:如果在消費的過程中發生異常,SOFATracer 將會通過自定義的 ConsumerExceptionHandler 異常處理器把異常資訊打到 tracer-self.log 中。
- 對於列印相關的引數條件設定,比如是否允許丟棄訊息、是否記錄丟失日誌的數量、是否記錄丟失日誌的 TraceId 和 RpcId、丟失日誌的數量達到某閾值進行一次日誌輸出等。
2.1.4、啟動 Disruptor
Disruptor 的啟動委託給了 AsyncCommonDigestAppenderManager#start 方法來執行。
public void start(final String workerName) {
this.threadFactory.setWorkName(workerName);
this.ringBuffer = this.disruptor.start();
}複製程式碼
檢視呼叫棧,看下 SOFATracer 中具體是在哪裡呼叫這個 start 的:
- CommonTracerManager : 這裡面持有了 AsyncCommonDigestAppenderManager 類的一個單例物件,並且在 static 靜態程式碼塊中呼叫了 start 方法;這個用來輸出普通中介軟體日誌。
- SofaTracerDigestReporterAsyncManager:這裡類裡面也是持有了AsyncCommonDigestAppenderManager 類的一個單例對像,並且提供了getSofaTracerDigestReporterAsyncManager 方法來獲取該單例,在這個方法中呼叫了 start 方法;該物件用來輸出摘要日誌。
2.1.5、釋出事件
釋出事件,也就意味著當前需要產生一個 span 記錄,這個過程也是在 finish 方法的呼叫棧中,也就是上圖中DiskReporterImpl#digestReport 這個方法。
AsyncCommonDigestAppenderManager asyncDigestManager = SofaTracerDigestReporterAsyncManager .getSofaTracerDigestReporterAsyncManager();
// ...asyncDigestManager.append(span);
// ...複製程式碼
這裡將 span 資料 append 到環形緩衝區,根據 AsyncCommonDigestAppenderManager 的初始化屬性,如果允許丟棄,則使用 tryNext 嘗試申請序列,申請不到丟擲異常;否則使用 next() 阻塞模式申請序列。下面是一個簡易的模擬圖:
2.1.6、小結
摘要日誌的落盤依賴於 Disruptor 的事件模型,當 span#finish 方法執行時,觸發 SofaTracer 的 report 行為;report 最終會將當前 span 資料放入 Disruptor 佇列中去,釋出一個 SofaTracerSpanEvent 事件。Disruptor 的消費者 EventHandler 實現類 Consumer 會監聽當前佇列事件,然後在回撥函式 onEvent 中將 span 資料重新整理到磁碟中。
2.2、統計日誌落盤實現
統計日誌的作用是為了監控統計使用,其記錄了當前跨度的呼叫次數、執行結果等資料。統計日誌是每隔一定時間間隔進行統計輸出的日誌,因此很容易想到是使用定期任務來執行的。這裡同樣來跟蹤下統計日誌列印的方法呼叫過程。
2.2.1、統計日誌的呼叫鏈路
AbstractSofaTracerStatisticReporter 的 doReportStat 方法是個抽象方法,那這裡又是與外掛擴充套件部分聯絡在一塊的:
可以看到 AbstractSofaTracerStatisticReporter 的實現類均是在 SOFATracer plugins 包下,也就是說統計日誌列印需要由不同的擴充套件外掛來定義實現。但是實際上不同的外掛在重寫 doReportStat 方法時也並非是直接將 span 資料 flush 到磁碟的,而是將 SofaTracerSpan 轉換成 StatMapKey 然後塞到了 AbstractSofaTracerStatisticReporter 中的一個 map 結構物件中。具體細節詳見:[AbstractSofaTracerStatisticReporter#addStat]。
2.2.2、統計日誌的列印模型
前面提到,統計日誌的落盤具有一定的週期性,因此在統計日誌落盤的設計上,SOFATracer 沒有像摘要日誌落盤那樣依賴於 Disruptor 來實現。下面先通過一張簡單的結構圖來看下摘要日誌的工作模型:
- xxxxxStatReporter : 外掛擴充套件方實現的統計日誌 Reporter 類,重寫了 doStatReport 和 print 兩個方法。
- AbstractSofaTracerStatisticReporter : 用於擴充套件的抽象類,xxxxxStatReporter 就是該類的子類;AbstractSofaTracerStatisticReporter 在其建構函式中,通過 SofaTracerStatisticReporterCycleTimesManager 將當前 statReporter 註冊到 SofaTracerStatisticReporterManager 中,統一存放在 statReporters 集合中。
- SofaTracerStatisticReporterManager : 統計日誌 reporter 管理器,所有外掛擴充套件的 reporter 都會被註冊到這個manager 類裡面來。其內部類 StatReporterPrinter 實現了runnable 介面,並在 run 方法中遍歷 statReporters,逐一呼叫 print 方法將資料刷到磁碟中。
SofaTracerStatisticReporterManager 在建構函式中初始化了任務執行的週期、ScheduledExecutorService 例項初始化,並且將 StatReporterPrinter 提交到定時任務執行緒池中,從而實現了週期性輸出統計日誌的功能。
3、上報 Zipkin
前面對 SOFATracer 中的資料落盤進行了分析,最後再來看下 SOFATracer 中是如何把資料上報至 zipkin 的。
3.1、上報 zipkin 的流程
接著上面的分析,SOFATracer 中的資料上報策略是以組合的形式共存的,這裡可以結合 第2節的第一張圖 來看。這裡先給出 zipkin 上報的流程,然後再結合流程展開分析:
- 在SofaTracer#reportSpan 中有一個方法是 invokeReportListeners;該方法的作用就是遍歷當前所有的SpanReportListener 實現類,逐一回撥 SpanReportListener 的 onSpanReport 方法。
- ZipkinSofaTracerSpanRemoteReporter 是 sofa-tracer-zipkin-plugin 外掛中提供的一個實現了 SpanReportListener 介面的類,並在 onSpanReport 回撥函式中通過 zipkin2.reporter.AsyncReporter 例項物件將 span 資料上報至 zipkin。
- 雖然 SOFATracer 和 zipkin 均是基於 OpenTracing 規範,但是在具體實現上 SOFATracer 做了很多擴充套件,因此需要通過一個 ZipkinV2SpanAdapter 將 SofaTracerSpan 適配成 zipkin2.Span。
zipkin2.reporter.AsyncReporter 是 zipkin 提供的一個資料上報抽象類,預設實現是 BoundedAsyncReporter,其內部通過一個守護執行緒 flushThread,一直迴圈呼叫 BoundedAsyncReporter 的 flush 方法,將記憶體中的 span 資訊上報給 zipkin。
3.2、對非 SpringBoot 應用的上報支援
上報 zipkin 的能力做過一次改動,主要是對於在非SpringBoot應用(也就是Spring工程)的支援,具體參考 issue:建議不用spring boot也可以使用sofa-tracer並且上報zipkin 。
對於 SpringBoot 工程來說,引入 tracer-sofa-boot-starter 之後,自動配置類 SofaTracerAutoConfiguration 會將當前所有 SpanReportListener 型別的 bean 例項儲存到 SpanReportListenerHolder 的 List 物件中。而SpanReportListener 型別的 Bean 會在 ZipkinSofaTracerAutoConfiguration 自動配置類中注入到當前 Ioc 容器中。這樣 invokeReportListeners 被呼叫時,就可以拿到 zipkin 的上報類,從而就可以實現上報。
對於非 SpringBoot 應用的上報支援,本質上是需要例項化 ZipkinSofaTracerSpanRemoteReporter 物件,並將此物件放在 SpanReportListenerHolder 的 List 物件中。所以 SOFATracer 在 zipkin 外掛中提供了一個ZipkinReportRegisterBean,並通過實現 Spring 提供的 bean 生命週期介面 InitializingBean,在ZipkinReportRegisterBean 初始化之後構建一個 ZipkinSofaTracerSpanRemoteReporter 例項,並交給SpanReportListenerHolder 類管理。
3.3、Zipkin 上報案例及展示
關於 SpringBoot 工程使用 zipkin 上報案例請參考:上報資料到 zipkin
關於 spring 應用中使用 zipkin 上報外掛請參考:tracer-zipkin-plugin-demo
- Services 展示
- 鏈路依賴展示
4、總結
4.1、SOFATracer 在資料上報模型上的考慮
瞭解或者使用過 SOFATracer 的同學應該知道, SOFATracer 目前並沒有提供資料採集器和 UI 展示的功能;主要有兩個方面的考慮:
- SOFATracer 作為 SOFA 體系中一個非常輕量的元件,意在將 span 資料以日誌的方式落到磁碟,以便於使用者能夠更加靈活的來處理這些資料
- UI 展示方面,SOFATracer 本身基於 OpenTracing 規範實現,在模型上與開源的一些產品可以實現無縫對接,在一定程度上可以彌補本身在鏈路視覺化方面的不足。
因此在上報模型上,SOFATracer 提供了日誌輸出和外部上報的擴充套件,方便接入方能夠足夠靈活的方式來處理上報的資料。
4.2、文章小結
通過本文大家對 SOFATracer 資料上報功能應該有了一個大體的瞭解,對於內部的實現細節,由於篇幅和文章閱讀性等原因,不宜貼過多程式碼,希望有興趣的同學可以直接閱讀原始碼,對其中的一些細節進行了解。資料上報作為 SOFATracer 核心擴充套件能力之一,雖不同的上報途徑對應不同的上報模型,但是整體結構上還是比較清晰的,所以理解起來不是很難。
最後感謝大家對 SOFATracer 的關注,如果您在瞭解和使用此元件的過程中有任何疑問,歡迎聯絡我們。
歡迎加入,參與 SOFATracer 原始碼解析【已領取完畢】
本文作為《剖析 | SOFATracer
元件系列》第一篇,主要還是希望大家對 SOFATracer
元件有一個認識和了解,之後,我們會逐步詳細介紹每部分的程式碼設計和實現,預計會按照如下的目錄進行:
-
分散式鏈路跟蹤元件
SOFATracer
概述【已完成】 -
SOFATracer API
元件埋點機制和原始碼分析【已完成】 -
SOFATracer
鏈路透傳原理與SLF4J MDC
的擴充套件能力分析【已領取】 -
SOFATracer
的取樣策略和原始碼分析【已領取】 -
SOFATracer
資料上報機制和原始碼分析【已領取】
文中提到的文字連結
- Disruptor : github.com/LMAX-Exchan…
- SofaTracerSpanEvent:github.com/alipay/sofa…
- StringEvent:github.com/alipay/sofa…
- AsyncCommonDigestAppenderManager:github.com/alipay/sofa…
- [AbstractSofaTracerStatisticReporter#addStat]:github.com/alipay/sofa…
- issue:建議不用spring boot也可以使用sofa-tracer並且上報zipkin:github.com/alipay/sofa…
- 上報資料到 zipkin:www.sofastack.tech/sofa-tracer…
- tracer-zipkin-plugin-demo:github.com/glmapper/tr…
長按關注,獲取分散式架構乾貨
歡迎大家共同打造 SOFAStack https://github.com/alipay