螞蟻金服分佈式鏈路跟蹤組件 SOFATracer 數據上報機制和源碼分析 | 剖析

SOFA
Scalable Open Financial Architecture
是螞蟻金服自主研發的金融級分佈式中間件,包含了構建金融級雲原生架構所需的各個組件,是在金融場景裏錘鍊出來的最佳實踐。java


SOFATracer 是一個用於分佈式系統調用跟蹤的組件,經過統一的 TraceId 將調用鏈路中的各類網絡調用狀況以日誌的方式記錄下來,以達到透視化網絡調用的目的,這些鏈路數據可用於故障的快速發現,服務治理等。
git


本文爲《剖析 | SOFATracer 框架》第二篇。《剖析 | SOFATracer 框架》系列由 SOFA 團隊和源碼愛好者們出品,項目代號:<SOFA:TracerLab/>目前領取已經完成,感謝你們的參與。github


SOFATracer:spring

https://github.com/alipay/sofa-tracerbash

0、前言

在《螞蟻金服分佈式鏈路跟蹤組件 SOFATracer 總覽|剖析》一文中已經對 SOFATracer 進行了概要性的介紹。從對 SOFATracer 的定義能夠了解到,SOFATracer 做爲一個分佈式系統調用跟蹤的組件,是經過統一的 TraceId 將調用鏈路中的各類網絡調用狀況以數據上報的方式記錄下來,以達到透視化網絡調用的目的。網絡

本篇將針對SOFATracer的數據上報方式進行詳細分析,以幫助你們更好的理解 SOFATracer 在數據上報方面的擴展。架構


一、Reporter 總體模型

本節將對 SOFATracer 的 Report 模型進行總體介紹,主要包括兩個部分:一、Reporter 的接口設計及實現;二、數據上報流程。app

1.一、Reporter 的接口設計及實現

數據上報是 SofaTracer 基於 OpenTracing Tracer 接口擴展實現出來的功能;Reporter 實例做爲 SofaTracer 的屬性存在,在構造 SofaTracer 實例時,會初始化 Reporter 實例。框架

1.1.一、Reporter 接口設計async

Reporter 接口是 SOFATracer 中對於數據上報的頂層抽象,核心接口方法定義以下:

//獲取 Reporter 實例類型
String getReporterType();
//輸出 span
void report(SofaTracerSpan span);
//關閉輸出 span 的能力
void close();複製代碼

Reporter 接口的設計中除了核心的上報功能外,還提供了獲取 Reporter 類型的能力,這個是由於 SOFATracer 目前提供的埋點機制方案須要依賴這個實現。

1.1.二、Reporter 接口實現

Reporter 的類體系結構以下:


Reporter 的實現類有兩個,SofaTracerCompositeDigestReporterImpl 和 DiskReporterImpl :

  • SofaTracerCompositeDigestReporterImpl:組合摘要日誌上報實現,上報時會遍歷當前 SofaTracerCompositeDigestReporterImpl 中全部的 Reporter ,逐一執行 report 操做;可供外部用戶擴展使用。
  • DiskReporterImpl:數據落磁盤的核心實現類,也是目前 SOFATracer 中默認使用的上報器。


1.二、數據上報流程分析

數據上報實際都是由不一樣的鏈路組件發起,關於插件擴展機制及埋點方式不是本篇範疇,就不展開了。這裏直接來看數據上報的入口。

在 Opentracing 規範中提到,Span#finish 方法是 span 生命週期的最後一個執行方法,也就意味着一個 span 跨度即將結束。那麼當一個 span 即將結束時,也是當前 span 具備最完整狀態的時候。因此在 SOFATracer 中,數據上報的入口就是 Span#finish 方法,這裏貼一小段代碼:

//SofaTracerSpan#finish
@Override
public void finish(long endTime) {
    this.setEndTime(endTime);
    //關鍵記錄:report span
    this.sofaTracer.reportSpan(this);
    SpanExtensionFactory.logStoppedSpan(this);
}複製代碼

在 finish 方法中,經過 SofaTracer#reportSpan 將當前 span 進行了上報處理。以這個爲入口,整個數據上報的調用鏈路以下圖所示:

整個上報調用流程其實並非很難,這裏留兩個問題:

  • 如何構造 clientRportor 和 serverReporter 的,依據是什麼?
  • 摘要日誌和統計日誌是怎麼落盤的?

第一個問題會在插件埋點解析篇中給出答案;第二個問題下面來看。


二、日誌落盤

前面已經提到,SOFATracer 自己提供了兩種上報模式,一種是落到磁盤,另一種是上報到zipkin。在實現細節上,SOFATracer 沒有將這兩種策略分開以提供獨立的功能支持,而是將兩種上報方式組合在了一塊兒,而後再經過配置參數來控制是否進行具體的上報邏輯,具體參考下圖:

本節未來剖析下日誌落盤的實現細節。日誌落盤又分爲摘要日誌落盤 和 統計日誌落盤;摘要日誌是每一次調用均會落地磁盤的日誌;統計日誌是每隔必定時間間隔進行統計輸出的日誌。

2.一、摘要日誌落盤

摘要日誌落盤是基於 Disruptor 高性能無鎖循環隊列實現的。SOFATracer 中,AsyncCommonDigestAppenderManager 類對 disruptor 進行了封裝,用於處理外部組件的 Tracer 摘要日誌打印。

關於 Disruptor 的原理及其自身的事件模型此處不展開分析,有興趣的同窗能夠自行查閱相關資料。這裏直接看下 SOFATracer 中是如何使用 Disruptor 的。

2.1.一、消息事件模型

SOFATracer 使用了兩種不一樣的事件模型,一種是 SOFATracer 內部使用的 StringEvent,一種是外部擴展使用的SofaTacerSpanEvent。詳見:SofaTracerSpanEvent & StringEvent

2.1.二、Consumer 消費者

Consumer 是 AsyncCommonDigestAppenderManager 的內部類;實現了 EventHandler 接口,這個 Consumer 做爲消費者存在,監聽事件,而後經過 TraceAppender 將 span 數據 flush 到磁盤。詳見:AsyncCommonDigestAppenderManager

2.1.三、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.四、啓動 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.五、發佈事件

發佈事件,也就意味着當前須要產生一個 span 記錄,這個過程也是在 finish 方法的調用棧中,也就是上圖中DiskReporterImpl#digestReport 這個方法。

AsyncCommonDigestAppenderManager asyncDigestManager = SofaTracerDigestReporterAsyncManager
            .getSofaTracerDigestReporterAsyncManager();
// ...
asyncDigestManager.append(span);
// ...複製代碼

這裏將 span 數據 append 到環形緩衝區,根據 AsyncCommonDigestAppenderManager 的初始化屬性,若是容許丟棄,則使用 tryNext 嘗試申請序列,申請不到拋出異常;不然使用 next() 阻塞模式申請序列。下面是一個簡易的模擬圖:

2.1.六、小結

摘要日誌的落盤依賴於 Disruptor 的事件模型,當 span#finish 方法執行時,觸發 SofaTracer 的 report 行爲;report 最終會將當前 span 數據放入 Disruptor 隊列中去,發佈一個 SofaTracerSpanEvent 事件。Disruptor 的消費者 EventHandler 實現類 Consumer 會監聽當前隊列事件,而後在回調函數 onEvent 中將 span 數據刷新到磁盤中。

2.二、統計日誌落盤實現

統計日誌的做用是爲了監控統計使用,其記錄了當前跨度的調用次數、執行結果等數據。統計日誌是每隔必定時間間隔進行統計輸出的日誌,所以很容易想到是使用按期任務來執行的。這裏一樣來跟蹤下統計日誌打印的方法調用過程。

2.2.一、統計日誌的調用鏈路

AbstractSofaTracerStatisticReporter 的 doReportStat 方法是個抽象方法,那這裏又是與插件擴展部分聯繫在一塊的:

能夠看到 AbstractSofaTracerStatisticReporter 的實現類均是在 SOFATracer plugins 包下,也就是說統計日誌打印須要由不一樣的擴展插件來定義實現。可是實際上不一樣的插件在重寫 doReportStat 方法時也並不是是直接將 span 數據 flush 到磁盤的,而是將 SofaTracerSpan 轉換成 StatMapKey 而後塞到了 AbstractSofaTracerStatisticReporter 中的一個 map 結構對象中。具體細節詳見:[AbstractSofaTracerStatisticReporter#addStat]

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 提交到定時任務線程池中,從而實現了週期性輸出統計日誌的功能。


三、上報 Zipkin

前面對 SOFATracer 中的數據落盤進行了分析,最後再來看下 SOFATracer 中是如何把數據上報至 zipkin 的。

3.一、上報 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.二、對非 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.三、Zipkin 上報案例及展現

關於 SpringBoot 工程使用 zipkin 上報案例請參考:上報數據到 zipkin

關於 spring 應用中使用 zipkin 上報插件請參考:tracer-zipkin-plugin-demo

  • Services 展現

  • 鏈路依賴展現


四、總結

4.一、SOFATracer 在數據上報模型上的考慮

瞭解或者使用過 SOFATracer 的同窗應該知道, SOFATracer 目前並無提供數據採集器和 UI 展現的功能;主要有兩個方面的考慮:

  • SOFATracer 做爲 SOFA 體系中一個很是輕量的組件,意在將 span 數據以日誌的方式落到磁盤,以便於用戶可以更加靈活的來處理這些數據
  • UI 展現方面,SOFATracer 自己基於 OpenTracing 規範實現,在模型上與開源的一些產品能夠實現無縫對接,在必定程度上能夠彌補自己在鏈路可視化方面的不足。

所以在上報模型上,SOFATracer 提供了日誌輸出和外部上報的擴展,方便接入方可以足夠靈活的方式來處理上報的數據。

4.二、文章小結

經過本文你們對 SOFATracer 數據上報功能應該有了一個大致的瞭解,對於內部的實現細節,因爲篇幅和文章閱讀性等緣由,不宜貼過多代碼,但願有興趣的同窗能夠直接閱讀源碼,對其中的一些細節進行了解。數據上報做爲 SOFATracer 核心擴展能力之一,雖不一樣的上報途徑對應不一樣的上報模型,可是總體結構上仍是比較清晰的,因此理解起來不是很難。

最後感謝你們對 SOFATracer 的關注,若是您在瞭解和使用此組件的過程當中有任何疑問,歡迎聯繫咱們。


 歡迎加入,參與 SOFATracer 源碼解析【已領取完畢】

本文做爲《剖析 | SOFATracer 組件系列》第一篇,主要仍是但願你們對 SOFATracer 組件有一個認識和了解,以後,咱們會逐步詳細介紹每部分的代碼設計和實現,預計會按照以下的目錄進行:

  • 分佈式鏈路跟蹤組件 SOFATracer 概述【已完成】

  • SOFATracer API 組件埋點機制和源碼分析【已完成】

  • SOFATracer 鏈路透傳原理與 SLF4J MDC 的擴展能力分析【已領取】

  • SOFATracer 的採樣策略和源碼分析【已領取】

  • SOFATracer 數據上報機制和源碼分析【已領取】


文中提到的文字連接

長按關注,獲取分佈式架構乾貨

歡迎你們共同打造 SOFAStack https://github.com/alipay

相關文章
相關標籤/搜索