Go?分布式鏈路追蹤實(shí)現(xiàn)原理解析
在分布式、微服務(wù)架構(gòu)下,應(yīng)用一個(gè)請(qǐng)求往往貫穿多個(gè)分布式服務(wù),這給應(yīng)用的故障排查、性能優(yōu)化帶來(lái)新的挑戰(zhàn)。分布式鏈路追蹤作為解決分布式應(yīng)用可觀測(cè)問(wèn)題的重要技術(shù),愈發(fā)成為分布式應(yīng)用不可缺少的基礎(chǔ)設(shè)施。本文將詳細(xì)介紹分布式鏈路的核心概念、架構(gòu)原理和相關(guān)開(kāi)源標(biāo)準(zhǔn)協(xié)議,并分享我們?cè)趯?shí)現(xiàn)無(wú)侵入 Go 采集 Sdk 方面的一些實(shí)踐。
為什么需要分布式鏈路追蹤系統(tǒng)
微服務(wù)架構(gòu)給運(yùn)維、排障帶來(lái)新挑戰(zhàn)
在分布式架構(gòu)下,當(dāng)用戶從瀏覽器客戶端發(fā)起一個(gè)請(qǐng)求時(shí),后端處理邏輯往往貫穿多個(gè)分布式服務(wù),這時(shí)會(huì)浮現(xiàn)很多問(wèn)題,比如:
- 請(qǐng)求整體耗時(shí)較長(zhǎng),具體慢在哪個(gè)服務(wù)?
- 請(qǐng)求過(guò)程中出錯(cuò)了,具體是哪個(gè)服務(wù)報(bào)錯(cuò)?
- 某個(gè)服務(wù)的請(qǐng)求量如何,接口成功率如何?

回答這些問(wèn)題變得不是那么簡(jiǎn)單,我們不僅僅需要知道某一個(gè)服務(wù)的接口處理統(tǒng)計(jì)數(shù)據(jù),還需要了解兩個(gè)服務(wù)之間的接口調(diào)用依賴關(guān)系,只有建立起整個(gè)請(qǐng)求在多個(gè)服務(wù)間的時(shí)空順序,才能更好的幫助我們理解和定位問(wèn)題,而這,正是分布式鏈路追蹤系統(tǒng)可以解決的。
分布式鏈路追蹤系統(tǒng)如何幫助我們
分布式鏈路追蹤技術(shù)的核心思想:在用戶一次分布式請(qǐng)求服務(wù)的調(diào)?過(guò)程中,將請(qǐng)求在所有子系統(tǒng)間的調(diào)用過(guò)程和時(shí)空關(guān)系追蹤記錄下來(lái),還原成調(diào)用鏈路集中展示,信息包括各個(gè)服務(wù)節(jié)點(diǎn)上的耗時(shí)、請(qǐng)求具體到達(dá)哪臺(tái)機(jī)器上、每個(gè)服務(wù)節(jié)點(diǎn)的請(qǐng)求狀態(tài)等等。

如上圖所示,通過(guò)分布式鏈路追蹤構(gòu)建出完整的請(qǐng)求鏈路后,可以很直觀地看到請(qǐng)求耗時(shí)主要耗費(fèi)在哪個(gè)服務(wù)環(huán)節(jié),幫助我們更快速聚焦問(wèn)題。
同時(shí),還可以對(duì)采集的鏈路數(shù)據(jù)做進(jìn)一步的分析,從而可以建立整個(gè)系統(tǒng)各服務(wù)間的依賴關(guān)系、以及流量情況,幫助我們更好地排查系統(tǒng)的循環(huán)依賴、熱點(diǎn)服務(wù)等問(wèn)題。

分布式鏈路追蹤系統(tǒng)架構(gòu)概覽
核心概念
在分布式鏈路追蹤系統(tǒng)中,最核心的概念,便是鏈路追蹤的數(shù)據(jù)模型定義,主要包括 Trace 和 Span。

其中,Trace 是一個(gè)邏輯概念,表示一次(分布式)請(qǐng)求經(jīng)過(guò)的所有局部操作(Span)構(gòu)成的一條完整的有向無(wú)環(huán)圖,其中所有的 Span 的 TraceId 相同。
Span 則是真實(shí)的數(shù)據(jù)實(shí)體模型,表示一次(分布式)請(qǐng)求過(guò)程的一個(gè)步驟或操作,代表系統(tǒng)中一個(gè)邏輯運(yùn)行單元,Span 之間通過(guò)嵌套或者順序排列建立因果關(guān)系。Span 數(shù)據(jù)在采集端生成,之后上報(bào)到服務(wù)端,做進(jìn)一步的處理。其包含如下關(guān)鍵屬性:
- Name:操作名稱,如一個(gè) RPC 方法的名稱,一個(gè)函數(shù)名
- StartTime/EndTime:起始時(shí)間和結(jié)束時(shí)間,操作的生命周期
- ParentSpanId:父級(jí) Span 的 ID
- Attributes:屬性,一組 <K,V> 鍵值對(duì)構(gòu)成的集合
- Event:操作期間發(fā)生的事件
- SpanContext:Span 上下文內(nèi)容,通常用于在 Span 間傳播,其核心字段包括 TraceId、SpanId
一般架構(gòu)
分布式鏈路追蹤系統(tǒng)的核心任務(wù)是:圍繞 Span 的生成、傳播、采集、處理、存儲(chǔ)、可視化、分析,構(gòu)建分布式鏈路追蹤系統(tǒng)。其一般的架構(gòu)如下如所示:

- 我們看到,在應(yīng)用端需要通過(guò)侵入或者非侵入的方式,注入 Tracing Sdk,以跟蹤、生成、傳播和上報(bào)請(qǐng)求調(diào)用鏈路數(shù)據(jù);
- Collect agent 一般是在靠近應(yīng)用側(cè)的一個(gè)邊緣計(jì)算層,主要用于提高 Tracing Sdk 的寫(xiě)性能,和減少 back-end 的計(jì)算壓力;
- 采集的鏈路跟蹤數(shù)據(jù)上報(bào)到后端時(shí),首先經(jīng)過(guò) Gateway 做一個(gè)鑒權(quán),之后進(jìn)入 kafka 這樣的 MQ 進(jìn)行消息的緩沖存儲(chǔ);
- 在數(shù)據(jù)寫(xiě)入存儲(chǔ)層之前,我們可能需要對(duì)消息隊(duì)列中的數(shù)據(jù)做一些清洗和分析的操作,清洗是為了規(guī)范和適配不同的數(shù)據(jù)源上報(bào)的數(shù)據(jù),分析通常是為了支持更高級(jí)的業(yè)務(wù)功能,比如流量統(tǒng)計(jì)、錯(cuò)誤分析等,這部分通常采用flink這類的流處理框架來(lái)完成;
- 存儲(chǔ)層會(huì)是服務(wù)端設(shè)計(jì)選型的一個(gè)重點(diǎn),要考慮數(shù)據(jù)量級(jí)和查詢場(chǎng)景的特點(diǎn)來(lái)設(shè)計(jì)選型,通常的選擇包括使用 Elasticsearch、Cassandra、或 Clickhouse 這類開(kāi)源產(chǎn)品;
- 流處理分析后的結(jié)果,一方面作為存儲(chǔ)持久化下來(lái),另一方面也會(huì)進(jìn)入告警系統(tǒng),以主動(dòng)發(fā)現(xiàn)問(wèn)題來(lái)通知用戶,如錯(cuò)誤率超過(guò)指定閾值發(fā)出告警通知這樣的需求等。
剛才講的,是一個(gè)通用的架構(gòu),我們并沒(méi)有涉及每個(gè)模塊的細(xì)節(jié),尤其是服務(wù)端,每個(gè)模塊細(xì)講起來(lái)都要很花些功夫,受篇幅所限,我們把注意力集中到靠近應(yīng)用側(cè)的 Tracing Sdk,重點(diǎn)看看在應(yīng)用側(cè)具體是如何實(shí)現(xiàn)鏈路數(shù)據(jù)的跟蹤和采集的。
協(xié)議標(biāo)準(zhǔn)和開(kāi)源實(shí)現(xiàn)
剛才我們提到 Tracing Sdk,其實(shí)這只是一個(gè)概念,具體到實(shí)現(xiàn),選擇可能會(huì)非常多,這其中的原因,主要是因?yàn)椋?/p>
- 不同的編程語(yǔ)言的應(yīng)用,可能采用不同技術(shù)原理來(lái)實(shí)現(xiàn)對(duì)調(diào)用鏈的跟蹤
- 不同的鏈路追蹤后端,可能采用不同的數(shù)據(jù)傳輸協(xié)議
當(dāng)前,流行的鏈路追蹤后端,比如 Zipin、Jaeger、PinPoint、Skywalking、Erda,都有供應(yīng)用集成的 sdk,導(dǎo)致我們?cè)谇袚Q后端時(shí)應(yīng)用側(cè)可能也需要做較大的調(diào)整。
社區(qū)也出現(xiàn)過(guò)不同的協(xié)議,試圖解決采集側(cè)的這種亂象,比如 OpenTracing、OpenCensus 協(xié)議,這兩個(gè)協(xié)議也分別有一些大廠跟進(jìn)支持,但最近幾年,這兩者已經(jīng)走向了融合統(tǒng)一,產(chǎn)生了一個(gè)新的標(biāo)準(zhǔn) OpenTelemetry,這兩年發(fā)展迅猛,已經(jīng)逐漸成為行業(yè)標(biāo)準(zhǔn)。

OpenTelemetry 定義了數(shù)據(jù)采集的標(biāo)準(zhǔn) api,并提供了一組針對(duì)多語(yǔ)言的開(kāi)箱即用的 sdk 實(shí)現(xiàn)工具,這樣,應(yīng)用只需要與 OpenTelemetry 核心 api 包強(qiáng)耦合,不需要與特定的實(shí)現(xiàn)強(qiáng)耦合。
應(yīng)用側(cè)調(diào)用鏈跟蹤實(shí)現(xiàn)方案概覽
應(yīng)用側(cè)核心任務(wù)
應(yīng)用側(cè)圍繞 Span,有三個(gè)核心任務(wù)要完成:
- 生成 Span:操作開(kāi)始構(gòu)建 Span 并填充 StartTime,操作完成時(shí)填充 EndTime 信息,期間可追加 Attributes、Event 等
- 傳播 Span:進(jìn)程內(nèi)通過(guò) context.Context、進(jìn)程間通過(guò)請(qǐng)求的 header 作為 SpanContext 的載體,傳播的核心信息是 TraceId 和 ParentSpanId
- 上報(bào) Span:生成的 Span 通過(guò) tracing exporter 發(fā)送給 collect agent / back-end server
要實(shí)現(xiàn) Span 的生成和傳播,要求我們能夠攔截應(yīng)用的關(guān)鍵操作(函數(shù))過(guò)程,并添加 Span 相關(guān)的邏輯。實(shí)現(xiàn)這個(gè)目的會(huì)有很多方法,不過(guò),在羅列這些方法之前,我們先看看在 OpenTelemetry 提供的 go sdk 中是如何做的。
基于 OTEL 庫(kù)實(shí)現(xiàn)調(diào)用攔截
OpenTelemetry 的 go sdk 實(shí)現(xiàn)調(diào)用鏈攔截的基本思路是:基于 AOP 的思想,采用裝飾器模式,通過(guò)包裝替換目標(biāo)包(如 net/http)的核心接口或組件,實(shí)現(xiàn)在核心調(diào)用過(guò)程前后添加 Span 相關(guān)邏輯。當(dāng)然,這樣的做法是有一定的侵入性的,需要手動(dòng)替換使用原接口實(shí)現(xiàn)的代碼調(diào)用改為包裝接口實(shí)現(xiàn)。
我們以一個(gè) http server 的例子來(lái)說(shuō)明,在 go 語(yǔ)言中,具體是如何做的:
假設(shè)有兩個(gè)服務(wù) serverA 和 serverB,其中 serverA 的接口收到請(qǐng)求后,內(nèi)部會(huì)通過(guò) httpclient 進(jìn)一步發(fā)起到 serverB 的請(qǐng)求,那么 serverA 的核心代碼可能如下圖所示:

以 serverA 節(jié)點(diǎn)為例,在 serverA 節(jié)點(diǎn)應(yīng)該產(chǎn)生至少兩個(gè) Span:
- Span1,記錄 httpServer 收到一個(gè)請(qǐng)求后內(nèi)部整體處理過(guò)程的一個(gè)耗時(shí)情況
- Span2,記錄 httpServer 處理請(qǐng)求過(guò)程中,發(fā)起的另一個(gè)到 serverB 的 http 請(qǐng)求的耗時(shí)情況
- 并且 Span1 應(yīng)該是 Span2 的 ParentSpan
我們可以借助 OpenTelemetry 提供的 sdk 來(lái)實(shí)現(xiàn) Span 的生成、傳播和上報(bào),上報(bào)的邏輯受篇幅所限我們不再詳述,重點(diǎn)來(lái)看看如何生成這兩個(gè) Span,并使這兩個(gè) Span 之間建立關(guān)聯(lián),即 Span 的生成和傳播 。
HttpServer Handler 生成 Span 過(guò)程
對(duì)于 httpserver 來(lái)講,我們知道其核心就是 http.Handler 這個(gè)接口。因此,可以通過(guò)實(shí)現(xiàn)一個(gè)針對(duì) http.Handler 接口的攔截器,來(lái)負(fù)責(zé) Span 的生成和傳播。
package http
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
http.ListenAndServe(":8090", http.DefaultServeMux)要使用 OpenTelemetry Sdk 提供的 http.Handler 裝飾器,需要如下調(diào)整 http.ListenAndServe 方法:
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
wrappedHttpHandler := otelhttp.NewHandler(http.DefaultServeMux, ...)
http.ListenAndServe(":8090", wrappedHttpHandler)

如圖所示,wrppedHttpHandler 中將主要實(shí)現(xiàn)如下邏輯(精簡(jiǎn)考慮,此處部分為偽代碼):
① ctx := tracer.Extract(r.ctx, r.Header):從請(qǐng)求的 header 中提取 traceparent header 并解析,提取 TraceId和 SpanId,進(jìn)而構(gòu)建 SpanContext 對(duì)象,并最終存儲(chǔ)在 ctx 中;
② ctx, span := tracer.Start(ctx, genOperation(r)):生成跟蹤當(dāng)前請(qǐng)求處理過(guò)程的 Span(即前文所述的Span1),并記錄開(kāi)始時(shí)間,這時(shí)會(huì)從 ctx 中讀取 SpanContext,將 SpanContext.TraceId 作為當(dāng)前 Span 的TraceId,將 SpanContext.SpanId 作為當(dāng)前 Span的ParentSpanId,然后將自己作為新的 SpanContext 寫(xiě)入返回的 ctx 中;
③ r.WithContext(ctx):將新生成的 SpanContext 添加到請(qǐng)求 r 的 context 中,以便被攔截的 handler 內(nèi)部在處理過(guò)程中,可以從 r.ctx 中拿到 Span1 的 SpanId 作為其 ParentSpanId 屬性,從而建立 Span 之間的父子關(guān)系;
④ span.End():當(dāng) innerHttpHandler.ServeHTTP(w,r) 執(zhí)行完成后,就需要對(duì) Span1 記錄一下處理完成的時(shí)間,然后將它發(fā)送給 exporter 上報(bào)到服務(wù)端。
HttpClient 請(qǐng)求生成 Span 過(guò)程
我們?cè)俳又?serverA 內(nèi)部去請(qǐng)求 serverB 時(shí)的 httpclient 請(qǐng)求是如何生成 Span 的(即前文說(shuō)的 Span2)。我們知道,httpclient 發(fā)送請(qǐng)求的關(guān)鍵操作是 http.RoundTriper 接口:
package http
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}OpenTelemetry 提供了基于這個(gè)接口的一個(gè)攔截器實(shí)現(xiàn),我們需要使用這個(gè)實(shí)現(xiàn)包裝一下 httpclient 原來(lái)使用的 RoundTripper 實(shí)現(xiàn),代碼調(diào)整如下:
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
wrappedTransport := otelhttp.NewTransport(http.DefaultTransport)
client := http.Client{Transport: wrappedTransport}
如圖所示,wrappedTransport 將主要完成以下任務(wù)(精簡(jiǎn)考慮,此處部分為偽代碼):
① req, _ := http.NewRequestWithContext(r.ctx, “GET”,url, nil) :這里我們將上一步 http.Handler 的請(qǐng)求的 ctx,傳遞到 httpclient 要發(fā)出的 request 中,這樣在之后我們就可以從 request.Context() 中提取出 Span1 的信息,來(lái)建立 Span 之間的關(guān)聯(lián);
② ctx, span := tracer.Start(r.Context(), url):執(zhí)行 client.Do() 之后,將首先進(jìn)入 WrappedTransport.RoundTrip() 方法,這里生成新的 Span(Span2),開(kāi)始記錄 httpclient 請(qǐng)求的耗時(shí)情況,與前文一樣,Start 方法內(nèi)部會(huì)從 r.Context() 中提取出 Span1 的 SpanContext,并將其 SpanId 作為當(dāng)前 Span(Span2)的 ParentSpanId,從而建立了 Span 之間的嵌套關(guān)系,同時(shí)返回的 ctx 中保存的 SpanContext 將是新生成的 Span(Span2)的信息;
③ tracer.Inject(ctx, r.Header):這一步的目的是將當(dāng)前 SpanContext 中的 TraceId 和 SpanId 等信息寫(xiě)入到 r.Header 中,以便能夠隨著 http 請(qǐng)求發(fā)送到 serverB,之后在 serverB 中與當(dāng)前 Span 建立關(guān)聯(lián);
④ span.End():等待 httpclient 請(qǐng)求發(fā)送到 serverB 并收到響應(yīng)以后,標(biāo)記當(dāng)前 Span 跟蹤結(jié)束,設(shè)置 EndTime 并提交給 exporter 以上報(bào)到服務(wù)端。
基于 OTEL 庫(kù)實(shí)現(xiàn)調(diào)用鏈跟蹤總結(jié)
我們比較詳細(xì)的介紹了使用 OpenTelemetry 庫(kù),是如何實(shí)現(xiàn)鏈路的關(guān)鍵信息(TraceId、SpanId)是如何在進(jìn)程間和進(jìn)程內(nèi)傳播的,我們對(duì)這種跟蹤實(shí)現(xiàn)方式做個(gè)小的總結(jié):

如上分析所展示的,使用這種方式的話,對(duì)代碼還是有一定的侵入性,并且對(duì)代碼有另一個(gè)要求,就是保持 context.Context 對(duì)象在各操作間的傳遞,比如,剛才我們?cè)?serverA 中創(chuàng)建 httpclient 請(qǐng)求時(shí),使用的是http.NewRequestWithContext(r.ctx, ...) 而非http.NewRequest(...)方法,另外開(kāi)啟 goroutine 的異步場(chǎng)景也需要注意 ctx 的傳遞。

非侵入調(diào)用鏈跟蹤實(shí)現(xiàn)思路
我們剛才詳細(xì)展示了基于常規(guī)的一種具有一定侵入性的實(shí)現(xiàn),其侵入性主要表現(xiàn)在:我們需要顯式的手動(dòng)添加代碼使用具有跟蹤功能的組件包裝原代碼,這進(jìn)一步會(huì)導(dǎo)致應(yīng)用代碼需要顯式的引用具體版本的 OpenTelemetry instrumentation 包,這不利于可觀測(cè)代碼的獨(dú)立維護(hù)和升級(jí)。
那我們有沒(méi)有可以實(shí)現(xiàn)非侵入跟蹤調(diào)用鏈的方案可選?
所謂無(wú)侵入,其實(shí)也只是集成的方式不同,集成的目標(biāo)其實(shí)是差不多的,最終都是要通過(guò)某種方式,實(shí)現(xiàn)對(duì)關(guān)鍵調(diào)用函數(shù)的攔截,并加入特殊邏輯,無(wú)侵入重點(diǎn)在于代碼無(wú)需修改或極少修改。

上圖列出了現(xiàn)在可能的一些無(wú)侵入集成的實(shí)現(xiàn)思路,與 .net、java 這類有 IL 語(yǔ)言的編程語(yǔ)言不同,go 直接編譯為機(jī)器碼,導(dǎo)致無(wú)侵入的方案實(shí)現(xiàn)起來(lái)相對(duì)比較麻煩,具體有如下幾種思路:
編譯階段注入:可以擴(kuò)展編譯器,修改編譯過(guò)程中的ast,插入跟蹤代碼,需要適配不同編譯器版本。啟動(dòng)階段注入:修改編譯后的機(jī)器碼,插入跟蹤代碼,需要適配不同 CPU 架構(gòu)。如 monkey, gohook。運(yùn)行階段注入:通過(guò)內(nèi)核提供的 eBPF 能力,監(jiān)聽(tīng)程序關(guān)鍵函數(shù)執(zhí)行,插入跟蹤代碼,前景光明!如,tcpdump,bpftrace。
Go 非侵入鏈路追蹤實(shí)現(xiàn)原理
Erda 項(xiàng)目的核心代碼主要是基于 golang 編寫(xiě)的,我們基于前文所述的 OpenTelemetry sdk,采用基于修改機(jī)器碼的的方式,實(shí)現(xiàn)了一種無(wú)侵入的鏈路追蹤方式。
前文提到,使用 OpenTelemetry sdk 需要代碼做一些調(diào)整,我們看看這些調(diào)整如何以非侵入的方式自動(dòng)的完成:

我們以 httpclient 為例,做簡(jiǎn)要的解釋。
gohook 框架提供的 hook 接口的簽名如下:
// target 要hook的目標(biāo)函數(shù)
// replacement 要替換為的函數(shù)
// trampoline 將源函數(shù)入口拷貝到的位置,可用于從replcement跳轉(zhuǎn)回原target
func Hook(target, replacement, trampoline interface{}) error
對(duì)于 http.Client,我們可以選擇 hook DefaultTransport.RoundTrip() 方法,當(dāng)該方法執(zhí)行時(shí),我們通過(guò) otelhttp.NewTransport() 包裝起原 DefaultTransport 對(duì)象,但需要注意的是,我們不能將 DefaultTransport 直接作為 otelhttp.NewTransport() 的參數(shù),因?yàn)槠?RoundTrip() 方法已經(jīng)被我們替換了,而其原來(lái)真正的方法被寫(xiě)到了 trampoline 中,所以這里我們需要一個(gè)中間層,來(lái)連接 DefaultTransport 與其原來(lái)的 RoundTrip 方法。具體代碼如下:
//go:linkname RoundTrip net/http.(*Transport).RoundTrip
//go:noinline
// RoundTrip .
func RoundTrip(t *http.Transport, req *http.Request) (*http.Response, error)
//go:noinline
func originalRoundTrip(t *http.Transport, req *http.Request) (*http.Response, error) {
return RoundTrip(t, req)
}
type wrappedTransport struct {
t *http.Transport
}
//go:noinline
func (t *wrappedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return originalRoundTrip(t.t, req)
}
//go:noinline
func tracedRoundTrip(t *http.Transport, req *http.Request) (*http.Response, error) {
req = contextWithSpan(req)
return otelhttp.NewTransport(&wrappedTransport{t: t}).RoundTrip(req)
}
//go:noinline
func contextWithSpan(req *http.Request) *http.Request {
ctx := req.Context()
if span := trace.SpanFromContext(ctx); !span.SpanContext().IsValid() {
pctx := injectcontext.GetContext()
if pctx != nil {
if span := trace.SpanFromContext(pctx); span.SpanContext().IsValid() {
ctx = trace.ContextWithSpan(ctx, span)
req = req.WithContext(ctx)
}
}
}
return req
}
func init() {
gohook.Hook(RoundTrip, tracedRoundTrip, originalRoundTrip)
}
我們使用 init() 函數(shù)實(shí)現(xiàn)了自動(dòng)添加 hook,因此用戶程序里只需要在 main 文件中 import 該包,即可實(shí)現(xiàn)無(wú)侵入的集成。
值得一提的是 req = contextWithSpan(req) 函數(shù),內(nèi)部會(huì)依次嘗試從 req.Context() 和 我們保存的 goroutineContext map 中檢查是否包含 SpanContext,并將其賦值給 req,這樣便可以解除了必須使用 http.NewRequestWithContext(...) 寫(xiě)法的要求。
詳細(xì)的代碼可以查看 Erda 倉(cāng)庫(kù):https://github.com/erda-project/erda-infra/tree/master/pkg/trace
參考鏈接
https://opentelemetry.io/registry/
https://opentelemetry.io/docs/instrumentation/go/getting-started/
https://www.ipeapea.cn/post/go-asm/
https://github.com/brahma-adshonor/gohook
https://www.jianshu.com/p/7b3638b47845
https://paper.seebug.org/1749/
到此這篇關(guān)于Go 分布式鏈路追蹤實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Go 分布式鏈路追蹤內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn)
本文主要介紹了golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
詳細(xì)介紹Go語(yǔ)言之?dāng)?shù)組與切片
這篇文章介紹Go語(yǔ)言之?dāng)?shù)組與切片,數(shù)組是具有相同唯一類型的一組已編號(hào)且長(zhǎng)度固定的數(shù)據(jù)項(xiàng)序列,這種類型可是任意的原始類型如整形、字符串或自定義類型。切片是數(shù)組的一個(gè)引用,因此切片是引用類型,在進(jìn)行傳遞時(shí),遵守引用傳遞的機(jī)制,下面我們就來(lái)詳細(xì)了解一下該內(nèi)容2021-10-10
Go項(xiàng)目與Docker結(jié)合實(shí)現(xiàn)高效部署深入探究
Go語(yǔ)言超時(shí)退出的三種實(shí)現(xiàn)方式總結(jié)
Go語(yǔ)言中TCP/IP網(wǎng)絡(luò)編程的深入講解
go?原子操作的方式及實(shí)現(xiàn)原理全面深入解析

