SOFA
Scalable Open Financial Architecture
是螞蟻金服自主研發的金融級分佈式中間件,包含了構建金融級雲原生架構所需的各個組件,是在金融場景裏錘鍊出來的最佳實踐。java
SOFATracer 是一個用於分佈式系統調用跟蹤的組件,經過統一的 TraceId 將調用鏈路中的各類網絡調用狀況以日誌的方式記錄下來,以達到透視化網絡調用的目的,這些鏈路數據可用於故障的快速發現,服務治理等。
git
本文爲《剖析 | SOFATracer 框架》第二篇。《剖析 | SOFATracer 框架》系列由 SOFA 團隊和源碼愛好者們出品,項目代號:<SOFA:TracerLab/>,目前領取已經完成,感謝你們的參與。github
SOFATracer:spring
https://github.com/alipay/sofa-tracerbash
在《螞蟻金服分佈式鏈路跟蹤組件 SOFATracer 總覽|剖析》一文中已經對 SOFATracer 進行了概要性的介紹。從對 SOFATracer 的定義能夠了解到,SOFATracer 做爲一個分佈式系統調用跟蹤的組件,是經過統一的 TraceId 將調用鏈路中的各類網絡調用狀況以數據上報的方式記錄下來,以達到透視化網絡調用的目的。網絡
本篇將針對SOFATracer的數據上報方式進行詳細分析,以幫助你們更好的理解 SOFATracer 在數據上報方面的擴展。架構
本節將對 SOFATracer 的 Report 模型進行總體介紹,主要包括兩個部分:一、Reporter 的接口設計及實現;二、數據上報流程。app
數據上報是 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 :
數據上報實際都是由不一樣的鏈路組件發起,關於插件擴展機制及埋點方式不是本篇範疇,就不展開了。這裏直接來看數據上報的入口。
在 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 進行了上報處理。以這個爲入口,整個數據上報的調用鏈路以下圖所示:
整個上報調用流程其實並非很難,這裏留兩個問題:
第一個問題會在插件埋點解析篇中給出答案;第二個問題下面來看。
前面已經提到,SOFATracer 自己提供了兩種上報模式,一種是落到磁盤,另一種是上報到zipkin。在實現細節上,SOFATracer 沒有將這兩種策略分開以提供獨立的功能支持,而是將兩種上報方式組合在了一塊兒,而後再經過配置參數來控制是否進行具體的上報邏輯,具體參考下圖:
本節未來剖析下日誌落盤的實現細節。日誌落盤又分爲摘要日誌落盤 和 統計日誌落盤;摘要日誌是每一次調用均會落地磁盤的日誌;統計日誌是每隔必定時間間隔進行統計輸出的日誌。
摘要日誌落盤是基於 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,使用的是 ProducerType.MULTI
//等待策略是 BlockingWaitStrategy,考慮到的是CPU的使用率和一致性
disruptor = new Disruptor<SofaTracerSpanEvent>(new SofaTracerSpanEventFactory(),
realQueueSize, threadFactory, ProducerType.MULTI, new BlockingWaitStrategy());複製代碼
Disruptor 的啓動委託給了 AsyncCommonDigestAppenderManager#start 方法來執行。
public void start(final String workerName) {
this.threadFactory.setWorkName(workerName);
this.ringBuffer = this.disruptor.start();
}複製代碼
查看調用棧,看下 SOFATracer 中具體是在哪裏調用這個 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.一、統計日誌的調用鏈路
AbstractSofaTracerStatisticReporter 的 doReportStat 方法是個抽象方法,那這裏又是與插件擴展部分聯繫在一塊的:
能夠看到 AbstractSofaTracerStatisticReporter 的實現類均是在 SOFATracer plugins 包下,也就是說統計日誌打印須要由不一樣的擴展插件來定義實現。可是實際上不一樣的插件在重寫 doReportStat 方法時也並不是是直接將 span 數據 flush 到磁盤的,而是將 SofaTracerSpan 轉換成 StatMapKey 而後塞到了 AbstractSofaTracerStatisticReporter 中的一個 map 結構對象中。具體細節詳見:[AbstractSofaTracerStatisticReporter#addStat]。
2.2.二、統計日誌的打印模型
前面提到,統計日誌的落盤具備必定的週期性,所以在統計日誌落盤的設計上,SOFATracer 沒有像摘要日誌落盤那樣依賴於 Disruptor 來實現。下面先經過一張簡單的結構圖來看下摘要日誌的工做模型:
SofaTracerStatisticReporterManager 在構造函數中初始化了任務執行的週期、ScheduledExecutorService 實例初始化,而且將 StatReporterPrinter 提交到定時任務線程池中,從而實現了週期性輸出統計日誌的功能。
前面對 SOFATracer 中的數據落盤進行了分析,最後再來看下 SOFATracer 中是如何把數據上報至 zipkin 的。
接着上面的分析,SOFATracer 中的數據上報策略是以組合的形式共存的,這裏能夠結合 第2節的第一張圖 來看。這裏先給出 zipkin 上報的流程,而後再結合流程展開分析:
zipkin2.reporter.AsyncReporter 是 zipkin 提供的一個數據上報抽象類,默認實現是 BoundedAsyncReporter,其內部經過一個守護線程 flushThread,一直循環調用 BoundedAsyncReporter 的 flush 方法,將內存中的 span 信息上報給 zipkin。
上報 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 類管理。
關於 SpringBoot 工程使用 zipkin 上報案例請參考:上報數據到 zipkin
關於 spring 應用中使用 zipkin 上報插件請參考:tracer-zipkin-plugin-demo
瞭解或者使用過 SOFATracer 的同窗應該知道, SOFATracer 目前並無提供數據採集器和 UI 展現的功能;主要有兩個方面的考慮:
所以在上報模型上,SOFATracer 提供了日誌輸出和外部上報的擴展,方便接入方可以足夠靈活的方式來處理上報的數據。
經過本文你們對 SOFATracer 數據上報功能應該有了一個大致的瞭解,對於內部的實現細節,因爲篇幅和文章閱讀性等緣由,不宜貼過多代碼,但願有興趣的同窗能夠直接閱讀源碼,對其中的一些細節進行了解。數據上報做爲 SOFATracer 核心擴展能力之一,雖不一樣的上報途徑對應不一樣的上報模型,可是總體結構上仍是比較清晰的,因此理解起來不是很難。
最後感謝你們對 SOFATracer 的關注,若是您在瞭解和使用此組件的過程當中有任何疑問,歡迎聯繫咱們。
本文做爲《剖析 | SOFATracer
組件系列》第一篇,主要仍是但願你們對 SOFATracer
組件有一個認識和了解,以後,咱們會逐步詳細介紹每部分的代碼設計和實現,預計會按照以下的目錄進行:
分佈式鏈路跟蹤組件 SOFATracer
概述【已完成】
SOFATracer API
組件埋點機制和源碼分析【已完成】
SOFATracer
鏈路透傳原理與 SLF4J MDC
的擴展能力分析【已領取】
SOFATracer
的採樣策略和源碼分析【已領取】
SOFATracer
數據上報機制和源碼分析【已領取】
長按關注,獲取分佈式架構乾貨
歡迎你們共同打造 SOFAStack https://github.com/alipay