天天看點

終于!SOFATracer 完成了它的鍊路可視化之旅

📄

文|趙陳(SOFA 開源之夏鍊路項目組)

武漢理工大學計算機工程碩士在讀

研究方向:唐卡線稿的自動上色

校對|宋國磊(SOFATracer commiter)

本文 6971 字 閱讀 18 分鐘

背 景

有幸參與開源軟體供應鍊點亮計劃——暑期 2021 支援的開源項目,目前 SOFATracer 已經能夠将埋點資料上報到 Zipkin 中,本項目的主要目标是将産生的埋點資料上報給 Jaeger 和 SkyWalking 中進行可視化展示。

PART. 1 SOFATracer

SOFATracer 是螞蟻集團基于 OpenTracing 規範開發的分布式鍊路跟蹤系統,其核心理念就是通過一個全局的 TraceId 将分布在各個服務節點上的同一次請求串聯起來。通過統一的 TraceId 将調用鍊路中的各種網絡調用情況以日志的方式記錄下來,以達到透視化網絡調用的目的,這些鍊路資料可用于故障的快速發現,服務治理等。

SOFATracer 提供了異步落地磁盤的日志列印能力和将鍊路跟蹤資料上報到開源産品 Zipkin 做分布式鍊路跟蹤展示的能力。這次參加開源之夏活動的任務是要把鍊路跟蹤資料上報到 Jaeger 和 SkyWalking 中進行展示。

SOFATracer 資料上報

終于!SOFATracer 完成了它的鍊路可視化之旅

上圖是 SOFATracer 中的鍊路上報流程,Span#finish 是 span 生命周期的最後一個執行方法,這是整個資料上報的入口,SOFATracer 的 report span 方法中含有上報鍊路展示端和日志落盤兩個部分。SOFATracer 中沒有把上報資料采集器和日志落盤分開隻是在日志落盤之前調用 SOFATracer#invokeReporListeners 方法,找到系統中所有實作了 SpanReportListener 接口并加入了 SpanReportListenersHolder 的執行個體,調用其 onSpanReport 方法完成鍊路資料上報至資料采集器。下面的代碼片段是 invokeReportListeners 方法的具體實作。

protected void invokeReportListeners(SofaTracerSpan sofaTracerSpan) {
    List<SpanReportListener> listeners = SpanReportListenerHolder
        .getSpanReportListenersHolder();
    if (listeners != null && listeners.size() > 0) {
        for (SpanReportListener listener : listeners) {
            listener.onSpanReport(sofaTracerSpan);
        }
    }
}           

SpanReportListenerHolder 中的執行個體在項目啟動的時候加入,且分為 Spring Boot 應用和 Spring 應用兩種情況:

  • 在 Spring Boot 應用中自動配置類 SOFATracerSpanRemoteReporter 會将目前所有 SpanReportListener 類型的 bean 執行個體儲存到 SpanReportListenerHolder 的 List 對象中。SpanReportListener 的執行個體對象會在各自的 AutoConfiguration 自動配置類中注入到 IOC 容器中。
  • 在 Spring 應用中通過實作 Spring 提供的 bean 生命周期接口 InitializingBean,在 afterPropertiesSet 方法中執行個體化 SpanReportListener 的執行個體對象并且加入到 SpanReportListenerHolder 中。

要實作把 SOFATracer 中的 trace 資料上傳到 Jaeger 和 SkyWalking 需要實作 SpanReportListener 接口并在應用啟動的時候把對應執行個體加入到 SpanReportListenersHolder 中。

PART. 2 Jaeger 資料上報

下圖是 Jaeger 中資料上報的部分圖示,圖中 CommandQueue 中存放的是重新整理或添加指令,生産者是采樣器和 flush 定時器,消費者是隊列處理器。采樣器判斷一個 span 需要上報後向 CommandQueue 中添加一個 AppendCommand,flush 定時器根據設定的 flushInterval 不斷向隊列中添加 FlushCommand,隊列處理器不斷從 CommandQueue 中讀取指令判斷是 AppendCommand 還是 FlushCommand,如果重新整理指令把目前 byteBuffer 中的資料發送到接受端,如果是添加指令把這個 span 添加到 byteBuffer 中暫存。

終于!SOFATracer 完成了它的鍊路可視化之旅

在實作上報到 Jaeger 過程中主要工作是 Jaeger Span 和 SOFATracer Span 模型的轉換,轉換過後利用上面的邏輯發送 span 到後端。

終于!SOFATracer 完成了它的鍊路可視化之旅

上圖是 Jaeger 中 Sender 的 UML 圖,從圖中可以看到有兩種類型的 Sender 分别是 HTTPSender 和 UDPSender 。分别對應用 HTTP 發送資料和 UDP 發送資料,在實作 SOFATracer 上報 Jaeger 中使用 UDPSender 發送 span 資料到 Jaeger Agent 中,使用 HTTPSender 直接發送資料到 Jaeger-Collector 中。

Jaeger Span 與 SOFATracer Span 模型的轉換

模型轉換對照

終于!SOFATracer 完成了它的鍊路可視化之旅

TraceId 和 SpanId 的處理

TraceId 的轉換:

  • 問題在 SOFATracer 中的 TracerId 的産生規則是:伺服器 IP + ID 産生的時間 + 自增序列 + 目前程序号
例如 :0ad1348f1403169275002100356696 前 8 位 0ad1348f 即産生 TraceId 的機器的 IP,這是一個十六進制的數字,每兩位代表 IP 中的一段,我們把這個數字,按每兩位轉成 10 進制即可得到常見的 IP 位址表示方式 10.209.52.143,您也可以根據這個規律來查找到請求經過的第一個伺服器。後面的 13 位 1403169275002 是産生 TraceId 的時間。之後的 4 位 1003 是一個自增的序列,從 1000 漲到 9000,到達 9000 後回到 1000 再開始往上漲。最後的 5 位 56696 是目前的程序 ID,為了防止單機多程序出現 TraceId 沖突的情況,是以在 TraceId 末尾添加了目前的程序 ID。——TraceId 和 SpanId 生成規則

在 SOFATracer 中 TraceId 是 String 類型,但是在 Jaeger 中 TraceId 是使用的兩個 Long 型的整數來構成最終的 TraceId。

解決方案

在 Jaeger 中表示 TraceId 的是 TraceIdHigh 與 TraceIdLow 在内部再使用函數将兩者轉換成 String 類型的 TraceIdAsString 在拼接的過程中分别将兩個 ID 轉換為對應的 HexString,當 HexString 不夠 16 位時頭部加 0。

StringBuilder builder = new StringBuilder(desiredLength);
    int offset = desiredLength - id.length();

    for (int i = 0; i < offset; i++)
        builder.append('0');
    builder.append(id);
    return builder.toString();
}           

SpanId 的轉化

  • 問題在 Jaeger 中 SpanId 是 Long 型整數,在 SOFATracer 中是 String 類型。
  • 解決辦法這個問題的解決辦法同之前已有的轉化為 Zipkin 中的 SpanId 的解決辦法一樣,也是使用 FNV Hash 将 String 映射成沖突較小的 Long 型。

兩種上傳方式

配合 Jaeger Agent

The Jaeger agent is a network daemon that listens for spans sent over UDP, which it batches and sends to the Collector. It is designed to be deployed to all hosts as an infrastructure component. The agent abstracts the routing and discovery of the Collectors away from the client.

Jaeger Agent 被設計成一種基本元件部署到主機上,能夠将路由和發現 Collector 的任務從 client 中抽離出來。Agent 隻能接受通過 UDP 發送的 Thrift 格式的資料,是以要使用 Jaeger Agent 需要使用 UDPSender。

使用 HTTP 協定上報 Collector

當使用 UDP 上報到 Jaeger Agent 的時候為了保證資料不在傳輸過程中丢失應該把 Jaeger Agent 部署在服務所在的機器,但是有的情況不能滿足前述要求,這時可以使用 HTTP 協定直接發送資料到 Collector,這時使用 HTTPSender。

PART. 3 SkyWalking 資料上報

SkyWalking 是分布式系統的應用程式性能監視工具,專為微服務、雲原生架構和基于容器架構而設計,提供分布式追蹤、服務網格遙測分析、度量聚合和可視化的一體化解決方案。SkyWalking 采用位元組碼注入的方式實作代碼的無侵入,且性能表現優秀。SkyWalking 的 receiver-trace 子產品可以通過 gRPC 和 HTTPRestful 服務接受 SkyWalking 格式的 trace 資料,在實作上報 SkyWalking 中選擇的上報方式是通過 HTTPRestful 服務上報。

終于!SOFATracer 完成了它的鍊路可視化之旅

SegmentId、SpanId、PatentSpanID 的轉換

SOFATracer 中的 SpanId 是一個字元串,但是在 SkyWalking 中 SpanId 和 ParentSpanId 是一個 int 整數并且每一個 segment 中的 SpanId 都是從 0 開始編号,SpanId 最大值由配置的一個 segment 中最多有多少 span 指定。在轉換過程中需要指定 SpanId,因為現在每一個 segment 中隻有一個 span,是以轉換生成的 segment 中的 span 的 ID 可以固定成 0。

SegmentId 是用來唯一辨別一個 segment 的,如果 segmentId 相同前一個 segment 會被後面的 segment 覆寫導緻 span 丢失。最後使用的 segmentId 的構造方式是 segmentId = traceId + SpanId 哈希值 + 0/1,其中 0 和 1 分别代表 server 和 client。最後需要加上 client 和 server 的原因是在 Dubbo 和 SOFARPC 中存在 server -> server 的情況,其中 RPC 調用的 client、server span 的 SpanId 和 parentId 都一樣,需要以此來區分它們,否則 client 端的 span 會被覆寫。

Dubbo 與 SOFARPC 的處理

基本的模型是 client-server-client-server-. 這種模式,但是在 Dubbo 和 SOFARPC 中存在 server -> server 的情況,其中 client span、server span 兩個 span 除了 kind 類型不同之外,其他的資訊是一樣。

  • parentSegmentId

要找出 parentSegmentId,在非 SOFARPC 和 Dubbo 情況下,遵循 server -> client, client -> server 也就是 client 的父 spa 隻能是 server 類型的,server 類型的父 span 隻能為空或 client 類型。轉換方式是在 SOFARPC 和 Dubbo 中,根據使用 SkyWalking Java Agent 上報時兩者的鍊路展示情況,轉化按照:

server span:parentSegmentId = traceId + parentId 哈希值 + client(1)

client span:parentSegmentId = traceId + parentId 哈希值 + server(0)

server span:parentSegmentId = traceId + spanId 哈希值 + client(1)

client span :parentSegmentId = traceId + parentId 哈希值 + server(0)

  • 字段和 networkAddressUsedAtPeer 字段:

Peer 字段

在 Dubbo 中 Peer 字段可以通過 remote.host、remote.port 兩個 tag 組成 SOFARPC 中在 remote.ip 中包含了 IP 和 port,隻使用 IP,因為在 server 端上報的 span 中無法獲得 client 使用的是自己的哪個端。

networkAddressUsedAtPeerDubbo

可以通過 local.host、local.port 組成 SOFARPC 中不能直接從 span 中擷取到本機的 IP,使用的是擷取本機的第一個有效 IPv4 位址,但是沒有端口号,是以在上面的 peer 字段中也隻用了 IP。

### 展示拓撲圖

在建構鍊路的過程中幾個比較關鍵的字段是 peer、networkAddressUsedAtPeer 、parentService、parentServiceInstance、parentEndpoint。其中 Peer 和 networkAddressUsedAtPeer 分别表示對端位址以及 client 端調用目前執行個體使用的位址,這兩個字段的作用是将鍊路中的執行個體連接配接起來,如果這兩個字段缺失會導緻鍊路斷開,在轉換過程中這兩個字段通過在 span 的 tag 中尋找或擷取本機第一個合法的 IPv4 位址獲得。後三個字段的作用是指出對應的父執行個體節點,如果不設定這三個字段會産生一個空的執行個體資訊,如下圖所示。目前 SOFATracer 中在能在上下文中傳播的隻有 TraceIdSpanId、parentId、sysBaggage、bizBaggage 從其中無法得到以上的三個字段,為了能展示拓撲圖在 SOFATracer 的上下文中增加了七個字段 service、serviceInstance、endpoint、parentService、parentServiceInstance、parentEndpoint、peer 這樣就能夠在轉換的過程中獲得父服務的相關資訊。

終于!SOFATracer 完成了它的鍊路可視化之旅

異步上傳

使用 HTTP 上報 Json 格式的 segment 資料到後端,上報時以 message 為機關,多個 segment 組合成一個 message。

流程如下圖,span 結束後将轉換好的 segment 加入到 segment 緩沖數組中,另一個線程不斷到數組中重新整理資料到 message,當 message 的大小達到最大值或等待發送的時間達到設定值就發送一次資料,設定的 message 最大預設為 2MB。

終于!SOFATracer 完成了它的鍊路可視化之旅

PART. 4 壓 測

測試配置

  • Windows 10
  • Memory 16G
  • Disk 500GB SSD
  • Intel(R) Core(TM) i7-7700HQ CPU @2.80GHz 2.80GHz

測試方式

部署一個包含六個服務的調用鍊路。設定三組對照:

  • 不采集 span
  • 50% 采集
  • 全量采集

Jaeger 測試結果

測試中相關的幾個參數設定如下:

終于!SOFATracer 完成了它的鍊路可視化之旅

Jaeger Agent 方式

終于!SOFATracer 完成了它的鍊路可視化之旅
終于!SOFATracer 完成了它的鍊路可視化之旅

不采集

終于!SOFATracer 完成了它的鍊路可視化之旅

上報 Jaeger Collector

終于!SOFATracer 完成了它的鍊路可視化之旅

50%采集

終于!SOFATracer 完成了它的鍊路可視化之旅
終于!SOFATracer 完成了它的鍊路可視化之旅

SkyWalking 測試結果

全集采集

終于!SOFATracer 完成了它的鍊路可視化之旅
終于!SOFATracer 完成了它的鍊路可視化之旅
終于!SOFATracer 完成了它的鍊路可視化之旅

測試小結

終于!SOFATracer 完成了它的鍊路可視化之旅

在全采樣時三種上報方式中上報 SkyWalking 的本機吞吐率是最低的隻有 512.75/sec,相比于上報 Jaeger Agent 吞吐率下降了約 14%,相比于上傳 Jaeger Agent 吞吐率減少了 11.89%。就每種方式對比全采樣與不采樣時吞吐率的變化:上報 Jaeger Agent 時因為全采樣吞吐率下降了 14.6%,上報 Jaeger Collector 時因為全采樣吞吐率下降了 17%,上報 SkyWalking 時因為全采樣吞吐率下降了約 23%。

本次介紹的 SOFATracer 的鍊路可視化,将會在下個版本 release。

-

「收獲」

很幸運能夠參加這次的開源之夏活動,在閱讀 SOFATracer 源碼的過程中學習了很多優秀的設計思想與實作方式,實作的過程中會去模仿一些源碼的實作方式在這個過程中自己學習到了很多。在項目實施過程中也發現了自己的一些問題,比如在解決問題時有一點思路就開始做,沒有深挖這個思路是否可行,這個壞習慣浪費了許多時間。這是我第一次參與到開源社群的相關活動中,在這個過程中了解了開源社群的運作方式,在以後的學習過程中會更加努力提高自己的代碼能力,争取能為開源社群做出一點貢獻。

特别感謝感謝宋國磊老師對我的耐心指導,在項目過程中宋老師幫助我解開了很多疑惑,學到很多東西,感謝 SOFAStack 社群在整個過程中對我的諸多幫助,感謝活動主辦方提供的平台。

「參考資料」

  1. 螞蟻集團分布式鍊路跟蹤元件 SOFATracer 資料上報機制和源碼分析 | 剖析
  2. 使用 SkyWalking 實作全鍊路監控
  3. Zipkin-SkyWalking Exporter
  4. STAM:針對大型分布式應用系統的拓撲自動檢測方法

本周推薦閱讀

終于!SOFATracer 完成了它的鍊路可視化之旅