歡迎光臨
每天分享高質量文章

螞蟻金服分佈式鏈路跟蹤組件鏈路透傳原理與SLF4J MDC的擴展能力分析 | 剖析

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

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

本文為《剖析 | SOFATracer 框架》第三篇,本篇作者J.Queue,來自端點科技。《剖析 | SOFATracer 框架》系列由 SOFA 團隊和原始碼愛好者們出品,專案代號:目前領取已經完成,感謝大家的參與。

SOFATracer:

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

 

SOFATracer 是一個用於分佈式系統呼叫跟蹤的組件,其核心作用就是能夠在分佈式場景下將請求經過的各個的鏈路環節的相關資料記錄下來,通過這些資料將各個呼叫鏈路相關的組件串聯起來。

在日常的開發中,我們除了跟蹤鏈路外,可能還會遇到一些場景:

例如在線壓測,我們在已有的系統的中,模擬一些請求(壓測流量)對我們的系統進行壓力測試,那麼在整個鏈路中我們是如何讓所有的系統都識別出當前的請求是壓測流量而不是正式流量的呢?壓測流量的標記又是如何在整個鏈路傳遞的呢?

又例如我們已經有了鏈路資料分析能力,能夠快速定位到某個請求是在 A 系統里出的問題,那麼我們怎麼從 A 系統的業務日誌里找到當前請求對應的業務日誌呢?

帶著這些問題,讓我們先來看看 SOFATracer 的鏈路透傳以及支持 SLF4J MDC 擴展能力。

SOFATracer 鏈路透傳原理

SOFATracer 的鏈路透傳具體包括兩個點:

  • 跨行程的透傳,即如何將鏈路資料從一個行程傳遞到下游行程中
  • 行程內的透傳
    • 當前請求跨行程呼叫結束之後,當前如何恢復 tracer 背景關係信息
    • 如何實現跨執行緒的透傳,如在當前執行緒中起一個異步執行緒的場景

跨行程鏈路透傳原理

跨行程透傳就是將上游系統的鏈路資料透傳到下游系統中,以便於提取出全域性的鏈路標記,如 TracerId 、採樣標記等,來實現將服務串聯起來並且保持傳輸過程中某些屬性的一致性。SOFATracer 基於 Opentracing 規範實現,因此在鏈路透傳部分,也是基於此規範;下麵就先從 Opentracing 規範中的透傳開始說起。

Opentracing 中的定義

在 OT 原文有這麼一段描述,傳送門:

Programmers adding tracing support across process boundaries must understand the Tracer.Inject(...) and Tracer.Extract(...) capabilities of the OpenTracing specification. They are conceptually powerful, allowing the programmer to write correct and general cross-process propagation code without being bound to a particular OpenTracing implementation; that said, with great power comes great opportunity for confusion. 

大概意思就是:如果開發者要給應用添加跨行程的追蹤能力, 首先要理解 OpenTracing 規範中的 Tracer.Inject(...) Tracer.Extract(…) 的功能。它們在概念抽象上非常強大,而且允許開發者編寫正確的、通用的跨行程傳輸的代碼,而不需要系結到特定的 OpenTracing 實現上去。 

總的來說就是 Opentracing 的 Tracer 接口定義了跨行程的能力,但是就是沒具體實現,不同的基於此規範實現的組件,需要遵循此規範來實現具體的透傳邏輯,下麵是 Tracer 接口定義的用於透傳的兩個方法:

接口 描述
void inject(SpanContext spanContext, Formatformat, C carrier); 把 spanContext 以指定的 format 的格式註入到 carrier 中
SpanContext extract(Format format, C carrier); 以指定的 format 的格式從 carrier 中解析出 SpanContext

行程透傳實現分析

SOFATracer 的 Tracer 的實現類是 SofaTracer, UML 圖如下:

從圖中可以看出 SofaTracer 除了有跨行程傳輸的能力,還擴展了資料上報的能力( Reporter )和採樣能力( Sampler )。資料上報能力可以參考 SOFATracer 資料上報機制和原始碼分析|剖析 這篇文章;採樣將在下一篇文章中進行剖析。

跨行程透傳的就是 SpanContext 的內容, carrier 為傳輸的載體, SpanContext 的實現類為 SofaTracerSpanContext, UML 圖:

跨行程透傳處理流程

SOFATracer 中跨行程傳輸的總體流程如下圖所示:

透傳原理的實質就是:呼叫方編碼將指定內容傳輸到被調方, 被調方解碼獲取內容的過程。

跨行程透傳的方式有很多, 在這裡以客戶端向服務端發起 HTTP 請求的方式來演示跨行程傳輸, fork 代碼, 打開 sample/tracer-sample-with-httpclient 示例工程運行 HttpClientDemoApplication ,打開 logs/tracelog/spring-mvc-stat.log 即可看到鏈路日誌, 運行結果 :

    1. {"time":"2019-01-07 19:42:50.134","stat.key":{"method":"GET","local.app":"HttpClientDemo","request.url":"http://localhost:8080/httpclient"},"count":1,"total.cost.milliseconds":1563,"success":"true","load.test":"F"}

{"time":"2019-01-07 20:09:46.285","stat.key":{"method":"GET","local.app":"HttpClientDemo","request.url":"http://localhost:8080/httpclient"},"count":1,"total.cost.milliseconds":71,"success":"true","load.test":"F"}

{"time":"2019-01-07 20:14:52.628","stat.key":{"method":"GET","local.app":"HttpClientDemo","request.url":"http://localhost:8080/httpclient"},"count":2,"total.cost.milliseconds":111,"success":"true","load.test":"F"}

透傳鏈路如下:

1、客戶端

首先找到客戶端攔截的入口類 

com.alipay.sofa.tracer.plugins.httpclient.interceptor.SofaTracerHttpInterceptor 

& com.alipay.sofa.tracer.plugins.httpclient.interceptor.SofaTracerAsyncHttpInterceptor

以 SofaTracerHttpInterceptor 為例:

  1. // 攔截請求

  2.  public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {

      //lazy init

  1.      RequestLine requestLine = httpRequest.getRequestLine();

  2.      String methodName = requestLine.getMethod();

  3.      // 生成SpanContext和Span

  4.      SofaTracerSpan httpClientSpan = httpClientTracer.clientSend(methodName);

         // 把 SpanContext inject到Carrier中

  1.      super.appendHttpClientRequestSpanTags(httpRequest, httpClientSpan);

     }

生成 Span 的最後一步是

 com.alipay.common.tracer.core.SofaTracer.SofaTracerSpanBuilder#start

  1. public Span start() {

  2.      SofaTracerSpanContext sofaTracerSpanContext = null;

  3.      // 判斷當前Span是否為鏈路中的root節點, 如果不是則創建子Span背景關係, 否則創建一個RootSpan背景關係

  4.      if (this.references != null && this.references.size() > 0) {

  5.          sofaTracerSpanContext = this.createChildContext();

  6.      } else {

  7.          sofaTracerSpanContext = this.createRootSpanContext();

  8.      }

  9.      long begin = this.startTime > 0 ? this.startTime : System.currentTimeMillis();

  10.      // 構建Span

  11.      SofaTracerSpan sofaTracerSpan = new SofaTracerSpan(SofaTracer.this, begin,

  12.                   this.references, this.operationName, sofaTracerSpanContext, this.tags);

  13.      // 採樣行為計算

  14.      boolean isSampled = calculateSampler(sofaTracerSpan);

  15.      sofaTracerSpanContext.setSampled(isSampled);

  16.      return sofaTracerSpan;

  17.  }

最後就是把 SpanContext 註入到 Carrier 中以 HTTP HEAD 的方式透傳到下游。

關於資料註入載體和從載體中提取資料可以參考 

com.alipay.common.tracer.core.registry.AbstractTextB3Formatter 類的實現。

2、服務端

找到服務端的攔截入口 SpringMvcSofaTracerFilter ,功能很簡單:

  • 獲取上游傳來的 SpanContext

  • 構建服務端的 Span,在這裡和客戶端做了同樣的判斷, 判斷當前 Span 是否為 RootSpan,這個操作很重要,如果是 RootSpan 則意味著一條新的鏈路要被構建;如果不是 RootSpan ,則會將當前服產生的 Span 通過 tracerId 關聯到當前鏈路中來。

執行緒透傳原理

在介紹執行緒透傳原理之前先來看個例子;對於 MVC 組件來說,如果我們想使用一個 Span 來記錄 mvc 的執行過程。一般我可以把 Span 的開始放在 Filter 中,filterChain.doFilter 方法執行之前產生,然後再 finally 塊中來結束這個 Span,大概如下:

  1. // Span span = null      //  1

  2. try{

  3.  // to create a new span

  4.  span = serverReceive()

  5.  // do something

  6.  filterChain.doFilter(servletRequest, responseWrapper);

  7.  // do something

  8. }finally{

  9.  // to finish current span

  10.  serverSend();

  11. }

假如現在有個問題是,在 serverReceive 和 serverSend 這段過程中涉及到了其他組件也產生了 Span,比如說發起了一次 httpclient 呼叫。大概對應的 tracer 如下:

  1. |mvcSpan|

        .

  2.     |httpclientSpan|

  3.            ...

  4.     |httpclientSpan|

  5.     .

  6. |mvcSpan|

這是典型的 childof 關係, httpclientSpan 、 childof mvcSpan 且都在同一個執行緒中執行。OK,解法:

  • 顯示的申明一個 Span ,如上面代碼段中 1 的位置。這樣 Span 的作用域足夠大,可以在 finally 中通過顯示呼叫 span#finish 來結束。

  • 使用 ThreadLocal 機制,在 serverReceive 中將當前 Span 放到 ThreadLocal 中,httpclientSpan 作用時,從 ThreadLocal 中先拿出 mvcSpan,然後作為 httpclientSpan 的父 Span 。此時將 httpclientSpan 塞到 ThreadLocal 中。當 httpclientSpan 結束時,再將 mvcSpan 複原到 ThreadLocal 中。

對於解法1 ,如果想在 httpclientSpan 的處理邏輯中使用 mvcSpan 怎麼辦呢?通過引數傳遞?那如果鏈路很長呢?顯然這種方式是不可取的。因此 SOFATracer 在實現上是基於 解法2 的方案來實現的。

綜合上面的案例,執行緒透傳可以從以下兩個角度來理解:

  • 當前執行緒中如果發生了跨行程呼叫(如 RPC 呼叫),那麼跨行程呼叫結束之後如何恢復 Tracer 背景關係信息

  • 當前執行緒執行過程中,又起了異步執行緒來執行一些子任務(如任務調度),如何將當前執行緒 Tracer 背景關係傳遞到子執行緒中

下麵就針對這兩個問題,來分析下 SOFATracer 的執行緒透傳實現。

執行緒透傳實現分析

在 SOFATracer 中定義了一個 SofaTraceContext 接口,允許應用程式訪問和操縱當前 Span 的狀態,預設實現是 SofaTracerThreadLocalTraceContext; UML 圖:

SofaTracerThreadLocalTraceContext 實際上就是使用了 ThreadLocal 來儲存當前執行緒 Tracer 背景關係信息的。下麵以 AbstractTracer#serverReceive 代碼片段來看下 SOFATracer 中存入 Span 的邏輯:

  1. public SofaTracerSpan serverReceive(SofaTracerSpanContext sofaTracerSpanContext) {

  2.      // 省略 ...

  3.      SofaTraceContext sofaTraceContext = SofaTraceContextHolder.getSofaTraceContext();

  4.      try {

  5.          // ThreadLocal 初始化, 存入當前的Span

  6.          sofaTraceContext.push(sofaTracerSpanServer);

  7.      } catch (Throwable throwable) {

  8.          // 省略 ...

  9.      } finally {

  10.          // 省略 ...

  11.      }

  12.      return sofaTracerSpanServer;

  13.  }

  • 通過 SofaTraceContextHolder 或到 SofaTraceContext  的實體物件,本質上就是 SofaTracerThreadLocalTraceContext 的單例物件

  • 將當前 Span 放入到 

    SofaTracerThreadLocalTraceContext,也就是存入 ThreadLocal 中

如果在後面的業務處理過程中需要用到此 Span ,那麼就可以通過SofaTraceContextHolder.getSofaTraceContext().getCurrentSpan() 這樣簡單的方式獲取到當前 Span 。

那麼既然是通過 ThreadLocal 來進行 tracer 背景關係的儲存,為了保證 ThreadLocal 不被污染,同時防止記憶體泄漏,需要在當前 Span 結束時清理掉當前 執行緒背景關係 中的資料。下麵通過 AbstractTracer#serverSend 代碼片段來看下 SOFATracer 中清理執行緒背景關係中透傳資料的邏輯:

  1. public void serverSend(String resultCode) {

  2.  try {

  3.    // 或取當前 SofaTraceContext 實體

  4.    SofaTraceContext sofaTraceContext = SofaTraceContextHolder.getSofaTraceContext();

  5.    // 取出 span 信息,這裡相當於就是恢復 tracer背景關係狀態信息

  6.    SofaTracerSpan serverSpan = sofaTraceContext.pop();

  7.    if (serverSpan == null) {

  8.        return;

  9.    //log

  1.    serverSpan.log(LogData.SERVER_SEND_EVENT_VALUE);

  2.    // 結果碼

  3.    serverSpan.setTag(CommonSpanTags.RESULT_CODE, resultCode);

  4.    serverSpan.finish();    

  5.    } finally {

  6.      //處理完成要清空 TL

  7.      this.clearTreadLocalContext();

  8.  }

  9. }

所以在整個執行緒處理過程中,SOFATracer 在 tracer 背景關係 處理上均是基於 Threadlocal 來完成的。

PS:

SofaTraceContext  中封裝了一系列用於操作 threadlocal 的工具方法,上面提到的 getCurrentSpan 和 pop 的區別在於,getCurrentSpan 從 threadlocal 中取出 Span 信息之後不會清理,也就是後面還可以通過getCurrentSpan 拿到當前執行緒背景關係中的 Span 資料,因此在業務處理過程中,如果需要向 Span 中添加一些鏈路資料,可以通過 getCurrentSpan 方法進行設置。pop 方法與 getCurrentSpan 實際上都是通過 threadlocal#get 來取資料的,當時 pop 取完之後會進行 clear 操作,因此 pop 一般用於在請求結束時使用。 

SpringMvcSofaTracerFilter 中在 finally 塊中呼叫了 serverSend ,serverSend 中就是使用的 pop 方法。

跨執行緒透傳

前一小節介紹了 tarcer 背景關係 如何實現在執行緒中透傳及恢復,那麼對於另外一種場景,即在當前執行緒處理過程中新起了子執行緒的情況,父執行緒如何將當前 tracer 背景關係信息傳遞到子執行緒中去呢?對於這種情況,SOFATracer 也提供了支持,下麵就來看下,SOFATracer 是如何實現跨執行緒傳遞的。

跨執行緒傳遞相對於跨進行傳遞來說要簡單的多,我們不需要考慮載體、格式化方式等;無論是父執行緒還是子執行緒,在儲存 tracer 背景關係 信息的實現上都是一樣的,都是基於 ThreadLocal 來儲存。但是為了保證當前 tracer 背景關係的狀態能夠在不同的執行緒中保持一致,不受干擾,SOFATracer 在將 tracer 背景關係傳遞到子執行緒中時,可以選擇使用的是當前父執行緒  tracer 背景關係 的克隆版本:

  1. public SofaTracerSpanContext cloneInstance() {

  2.  // 重新構建一個 SofaTracerSpanContext 物件實體

  3.  // 這裡會以當前父執行緒中的 tracerId,spanId,parentId以及採樣信息 作為構建構建引數

  4.  SofaTracerSpanContext spanContext = new SofaTracerSpanContext(this.traceId, this.spanId,

  5.  this.parentId, this.isSampled);

  6.  // 系統透傳資料

  7.  spanContext.addSysBaggage(this.sysBaggage);

  8.  // 業務透傳資料

  9.  spanContext.addBizBaggage(this.bizBaggage);

  10.  spanContext.childContextIndex = this.childContextIndex;

  11.  return spanContext;

  12. }

這裡會根據當前 SofaTracerSpanContext 實體的基本信息,重新 new 一個新的物件出來,是一種深拷貝的方式,實現了不同執行緒 tracer 背景關係處理的隔離。

另外,SOFATracer 還提供了 SofaTracerRunnable&SofaTracerCallable; 這兩個類 ,封裝了底層手動將 Span 複製到被調執行緒的 ThreadLocal 中去的過程;需要註意的是這個傳遞的是 Span ,並非是 SpanContext,因此也就沒有上面隔離一說,具體使用參考官方文件:異步執行緒處理。這裡以 SofaTracerRunnable 類來進行具體實現分析,SofaTracerCallable 大家可以自己去研究下。

關於異步執行緒處理的案例,可以參考 SOFATracer 的測試用例 。

  1. private void initRunnable(Runnable wrappedRunnable, SofaTraceContext traceContext) {

     // 任務 runnable

  1.  this.wrappedRunnable = wrappedRunnable;

  2.  // tracer 背景關係,可以由外部指定,如果沒有指定則使用 SofaTraceContextHolder 獲取

  3.  this.traceContext = traceContext;

  4.  if (!traceContext.isEmpty()) {

  5.    // 將當前背景關係中的 span 賦值給子執行緒

  6.      this.currentSpan = traceContext.getCurrentSpan();

  7.  } else {

  8.      this.currentSpan = null;

  9.  }

  10. }

這上面這段代碼片段來看,在構建 SofaTracerRunnable 物件實體時,會把當前父執行緒中的 traceContext 、currentSpan 等傳遞到子執行緒中。SofaTracerRunnable#run 方法中,會根據執行緒 ID 進行判斷,如果與父執行緒的執行緒ID不等,則會將 currentSpan push 到 traceContext (註:currentSpan 和 traceContext 均是子執行緒屬性),run 方法則是委托給用戶傳遞進來的 wrappedRunnable 來執行。

Opentracing 0.30.x 版本對於執行緒透傳的支持

對於在低版本 Opentracing 規範中並沒有對執行緒傳遞的支持,但是在 0.30.0 版本以後有支持。
SOFATracer 目前是基於 Opentracing 0.22.0 版本實現的;但是對於 Opentracing 新 API 中提供的執行緒透傳的特性的理解也會有助於 SOFATracer 在執行緒透傳方面的改進

在之前的文章中對於 Span 的層級關係有過介紹,如果按照時序關係來展示大概如下:

這裡以 A、B、D 來看,三個 Span 是逐級嵌套的;如果把這個模型理解成為一個棧的話,那麼各個 Span 的產生過程即為入棧的過程,如下:

由於棧的特性是 FILO ,因此當 span C 出棧時就意味著 span C 的生命周期結束了,此時會觸發 Span 資料的上報。這裡其實也很好的解釋了 ChildOf 這種關係的描述:父級 Span 某種程度上取決於子 Span  (子 Span 的結果可能會對父 Span 產生影響) ;父 Span 的生命周期時間是包含了子 Span 生命周期時間的。

在 SOFATracer 0.30.x 版本中提供了對上述思路的封裝,用於解決 Span 在執行緒中傳遞的問題。兩個核心的接口是Scope 和 ScopeManager ,Opentracing 中對這兩個接口均提供了預設的實現類:

ThreadLocalScope 和 ThreadLocalScopeManager 。

  • 使用 ThreadLocal 來儲存不同執行緒的 Scope 物件,在多執行緒環境下可以通過獲取到當前執行緒的 Scope 來獲取當前執行緒的活動的 Span。

  • 管理著當前執行緒所有曾被激活還未釋放的 Span(處於生命周期內的 Span )

ThreadLocalScopeManager & ThreadLocalScope 的設計

ScopeManager 解決的是 Span 在執行緒中傳遞的問題。但是 ScopeManager 本身直接操作 Span 又會顯得有些不徹底。這個不徹底怎麼理解呢?結合 SOFATracer 的實現,我的理解是:

  • SOFATracer 中也是使用 ThreadLocal 的機制實現 Span 在執行緒中傳遞的。ThreadLocal 中就是 set & get 。Span 之間的父子關係以及當前 ThreadLocal 中應該存哪個 Span 都需要我們自己在代碼中來管理。這種方式完全 OK,但是如果對於一個標準/規範來說,如果只是定義一個這樣的 ThreadLocal 完全是沒有意義的。

  • 自己管理 ThreadLocal 中 Span 的關係是一個複雜的過程,尤其是在鏈路較長的情況下。

基於上述兩點,ot-api 沒有採用直接在 ScopeManager 中基於 ThreadLocal 使用 set&get; span 的操作方案。而是使用了 Scope,對應的實現類是 ThreadLocalScope;那麼好處在哪呢?

ThreadLocalScope 的設計使用了棧的思想,這個怎麼理解呢?在一個執行緒中,每一個 Span 的產生到結束,裡面在嵌套子 Span 的產生到結束,這種嵌套關係可以很容器聯想到棧的概念;參考上圖,這個過程很好理解,棧的操作,有進有出,一進一齣就是一個 Span 的生命周期。

相比於 SOFATracer 的實現來看,Opentracing 提供的執行緒透傳實現更具有全域性性;ThreadLocalScope 為 Span 在執行緒中傳遞提供了新的設計思路,但是如果僅基於 Span + ThreadLocal 來實現,是很難的。

MDC 的擴展能力分析

SLF4J 提供了 MDC(Mapped Diagnostic Contexts)功能,可以支持用戶定義和修改日誌的輸出格式以及內容。SOFATracer 集成了 SLF4J MDC 功能,方便用戶在只簡單修改日誌配置檔案的情況下就可以輸出當前 Tracer 背景關係的 TraceId 和 SpanId。

SLF4J MDC 機制

MDC ( Mapped Diagnostic Contexts ),這個接口是為了便於我們診斷線上問題而出現的方法工具類。 MDC 的實現也是利用了 ThreadLocal 機制。 在代碼中,只需要將指定的值 put 到執行緒背景關係的 Map 中,然後在對應的地方使用 get 方法獲取對應的值。

先看一個 logback.xml 的輸出模板配置:

  1. name="console" class="ch.qos.logback.core.ConsoleAppender">

  2.   charset="UTF-8">

  3.    [%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5p) %logger.%M\(%F:%L\)] %X{THREAD_ID} %msg%n

  •  

在日誌模板 logback.xml 中,使用 %X{} 來占位,內容替換為對應的 MDC 中 key 的值,在模板解析時會從 MDC 中去取 key 對應的 value 來替換占位符以達到自定義日誌格式的效果。

MDC 在 SOFATracer 中的應用

SOFATracer 對 MDC 的擴展在 com.alipay.common.tracer.extensions.log.MDCSpanExtension,這個類利用了 SpanExtension 的擴展功能來實現。MDC 擴展的代碼也比較簡單,就是對 MDC 執行緒背景關係值的儲存和刪除操作,看兩段主要的:

  1.  // span 開始時的MDC操作

  2.  public void logStartedSpan(Span currentSpan) {

  3.      if (currentSpan != null) {

             SofaTracerSpan span = (SofaTracerSpan) currentSpan;

             SofaTracerSpanContext sofaTracerSpanContext = span.getSofaTracerSpanContext();

             if (sofaTracerSpanContext != null) {

              // 把當前span的traceId 和 spanId 放到 MDC 中

  1.              MDC.put(MDCKeyConstants.MDC_TRACEID, sofaTracerSpanContext.getTraceId());

  2.              MDC.put(MDCKeyConstants.MDC_SPANID, sofaTracerSpanContext.getSpanId());

  3.  }}}

  4.  // Span結束時的MDC操作

  5.  public void logStoppedSpan(Span currentSpan) {

  6.      // 把當前span的traceId 和 spanId 從 MDC 中移除

  7.      MDC.remove(MDCKeyConstants.MDC_TRACEID);

  8.      MDC.remove(MDCKeyConstants.MDC_SPANID);

  9.      if (currentSpan != null) {

  10.          SofaTracerSpan span = (SofaTracerSpan) currentSpan;

  11.          SofaTracerSpan parentSpan = span.getParentSofaTracerSpan();

  12.          if (parentSpan != null) {

                 SofaTracerSpanContext sofaTracerSpanContext = parentSpan.getSofaTracerSpanContext();

  1.              if (sofaTracerSpanContext != null) {

  2.                  // 把父span的traceId 和 spanId 放入 MDC 中

  3.                  MDC.put(MDCKeyConstants.MDC_TRACEID, sofaTracerSpanContext.getTraceId());

  4.                  MDC.put(MDCKeyConstants.MDC_SPANID, sofaTracerSpanContext.getSpanId());

  5.  }}}}

然後修改 logback.xml 的格式運算式:

  1. name="console" class="ch.qos.logback.core.ConsoleAppender">

  2.   charset="UTF-8">

  3.    

  4.    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{SOFA-TraceId},%X{SOFA-SpanId}]  %logger{50} - %msg%n

  •  

對應的 Demo 在 tracer-samples/tracer-sample-with-slf4j 下。

小結

回頭看文章開頭的兩個問題,基於 SOFATracer 的資料透傳和 MDC 擴展能力已經有瞭解決方案:

在線壓測的時候,我們只需要在入口往 SOFATracer 內設置一個壓測標識,通過 SOFATracer 的鏈路資料透傳能力,將壓測標識透傳到整個呼叫鏈路,每個呼叫鏈路相關的組件識別這個壓測標識進行對應的處理即可。

在業務日誌中找到請求相關的日誌,只需要在業務日誌輸出的時候,同步輸出 SpanId 和  TracerId,就能標記業務日誌的位置,再通過和 Tracer 信息的結合,快速定位問題。

本篇主要剖析了 SOFATracer 在資料透傳和 Slf4j MDC 擴展功能兩個點;在鏈路資料透傳部分,又分別對 跨行程透傳、執行緒透傳和 Opentracing 提供的執行緒透傳等分別作了詳細的介紹和分析。Slf4j MDC 擴展部分介紹了 MDC 機制以及 MDC 在 SOFATracer 中的應用。通過本篇,希望可以幫助大家更好的理解 SOFATracer 在鏈路透傳方面的基本原理和實現。

 

文中涉及到的所有鏈接:

  • 在 OT 原文描述 傳送門:

    https://opentracing.io/docs/overview/inject-extract/

  • 螞蟻金服分佈式鏈路跟蹤組件 SOFATracer 資料上報機制和原始碼分析| 剖析

  • SOFATracer 原始碼:

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

  • SOFAtrace的異步處理:

    https://www.sofastack.tech/sofa-tracer/docs/Async

  • SOFATracer 的測試用例 :

    https://github.com/alipay/sofa-tracer/tree/master/tracer-core/src/test/java/com/alipay/common/tracer/core/async

  • SOFATracer 對 MDC 的擴展 demo :

    https://github.com/alipay/sofa-tracer/tree/master/tracer-samples/tracer-sample-with-slf4j

赞(0)

分享創造快樂