Spring Cloud系列(十四):Sleuth 分布式跟蹤原理分析


轉自:

http://www.gxitsky.com/2019/05/01/springcloud-14-tracing-sleuth-zipkin-principle/

Sleuth 通過 traceId 實現了對分布式系統調用鏈路的跟蹤。在一次服務請求鏈路中,會保持並傳遞一個 traceId,從而將不同服務的請求跟蹤信息串聯起來,不同服務的 traceId 相同表示處在同一請求鏈中。

  基於 HTTP 請求的數據傳遞有兩種方式:一種是做為參數傳遞,另一種是做為頭信息傳遞。而 Sleuth 的 traceId 屬於附加信息,不參與實際的業務,所以做為參數傳遞並不合適,實際也是作為頭信息來傳遞的。

Request Header

參考 Spring Cloud系列(十三):分布式服務鏈路跟蹤 Sleuth 中的示例項目,Sleuth 會在請求的 Header 中增加實現跟蹤需的信息,給遠程調用接口打上斷點,使用 request.getHeaderNames() 取出所有頭信息。

Headers

1
["x-b3-spanid","x-b3-parentspanid","x-b3-sampled","x-b3-traceid","accept","user-agent","host","connection"]

springcloud-14-sleuth-request-header-traceid.png

 

可以看到,在請求頭信息中多了 4 個屬性:

  • x-b3-spanid:一個工作單元(rpc 調用)的唯一標識。
  • x-b3-parentspanid:當前工作單元的上一個工作單元,Root Span(請求鏈路的第一個工作單元)的值為空。
  • x-b3-traceid:一條請求鏈條(trace) 的唯一標識。
  • x-b3-sampled:是否被抽樣為輸出的標志,1 為需要被輸出,0 為不需要被輸出。

日志跟蹤接入

Sleuth 會把跟蹤數據 (appname、traceId、spanId、exportable) 添加到 Slf4J MDC 中,因此您可以從日志聚合器中的給定跟蹤或跨度中提取所有日志,如以下示例日志中所示:

1
2
3
2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...

MDC 的實現實際是將需要記錄到日志的信息設置到當前線程的上下文(ThreadContext)中。

MDC 中的信息:[appname,traceId,spanId,exportable]

  • appname:應用名稱,即 spring.application.name 的值。
  • tranceId:整個請求鏈路的唯一ID。
  • spanId:基本的工作單元,一個 RPC 調用就是一個新的 span。啟動跟蹤的初始 span 稱為 root span ,此 spanId 的值與 traceId 的值相同。
  • exportable:是否將數據導入到 Zipkin 中,true 表示導入成功,false 表示導入失敗。

Sleuth 跟蹤原理

分布式系統中的服務調用鏈路跟蹤在理論上並不復雜,主要有個關鍵點,一個是為請求鏈路創建唯一跟蹤標識,二個統計各個處理單元的延遲時間。

  1. 為了實現請求鏈路跟蹤,當請求發送到分布式系統的入口時,只需要在服務跟蹤框架為該請求創建唯一的跟蹤標識,並保證該標識在在分布式系統內部流轉,直到返回請求為止。該標識即為 traceId,通過該標識,就能將不同服務調用的日志串聯起來。
  2. 為了統計各處理單元(應用服務)的延遲,當請求到達或處理邏輯達到某個狀態時,也通過一個唯一標識來標記開始、具體過程及結束(標識一個服務內請求進入、處理到結束),該標識即為 spanId。對於每個 spanId 來說,必須有開始和結束兩個節點,通過計算開始 span 和 結束 span 的時間戳,就能統記出該 span 的時間延遲。

Sleuth 采樣比例

跟蹤信息收集默認是 0.1(10%) 的采樣比例,可通過 probability 屬性修改;或可采用每秒速率來控制采集數據,屬性是 rate。

1
2
3
4
# 跟蹤信息收集采樣比例,默認 0.1,為 1 是即 100%,收集所有
spring.sleuth.sampler.probability=1
# 每秒速率,即每秒最多能跟蹤的請求,rate 優先
spring.sleuth.sampler.rate=50

Brave 分布式跟蹤

從版本 2.0.0 開始,Spring Cloud Sleuth 使用 Brave 作為跟蹤庫。 因此,Sleuth 不再負責存儲上下文,而是將該工作委托給 Brave。
由於 Sleuth 與 Brave 有不同的命名和標記慣例,我們決定從現在開始遵循 Brave 的慣例。 但是,如果要使用傳統的偵聽方法,可以將 spring.sleuth.http.legacy.enabled 屬性設置為 true

Span 上下文傳播

Span Context(上下文) 必須傳播到跨進程邊界的任何 子 Span 狀態。Span Context 還是一部分是 Baggage。Trace ID 和 Span ID 是 Span Context 必需的,Baggage 是可選的。

Baggage 是一組存儲在 Span Context 中的 key:value(鍵值對)。Baggage 與 Traceg 一起移動並附在每個 Span 上。Spring Cloud Sleuth 可以識別以 baggage- 為前綴的 HTTP 頭,這是與 baggage 相關的頭,消息傳遞以 baggage_ 開始。

目前,Baggage 的數量和大小沒有限制。但是,太多會降低系統吞吐量或增加 RPC 延遲。在極端情況下,過多的 Baggage 會導致應用程序崩潰,因為超過了傳輸級別的消息或報頭容量。

在 Span Context 中設置 Baggage 示例:

1
2
3
Span initialSpan = this.tracer.nextSpan().name("span").start();
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");

Baggage 與 Span Tags

Baggage 跟隨 Trace 一起移動(每個子 span 都包含 父 span 的 Baggage)。Zipkin 不知道 baggage ,也不接收這些信息。

注意:從 Sleuth 2.0.0 開始,必須在項目配置中明確傳遞 baggage 鑰匙名稱。

Tags 附加到指定的 span ,也就是該標簽只在指定的 span 呈現。但是,如果包含 Tag 的 span 存在,則可以根據 Tag 搜索 trace

如果希望能夠根據 baggage 查找 span*,則應在 *root span 中添加相應的元素作為 Tag

baggage 集成示例:

1
2
spring.sleuth.baggage-keys=baz,bizarrecase
spring.sleuth.propagation-keys=foo,upper_case
1
2
initialSpan.tag("foo",ExtraFieldPropagation.get(initialSpan.context(), "foo"));
initialSpan.tag("UPPER_CASE",ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM