為什么需要鏈路追蹤?
我們程序員在日常工作中,最常做事情之一就是修bug了。如果程序只是運行在單機上,我們最常用的方式就是在程序上打日志,然后程序運行的過程中將日志輸出到文件上,然后我們根據日志去推斷程序是哪一步發生了問題。但是如果我們的程序是部署在分布式架構的各個服務上,我們再用這種方法去查看一個又一個日志文件,這就顯得非常的低效了。所以這時候如果有一個可以幫助我們根據時間脈絡將所有的信息都匯集起來并以可視化的方式直觀展示給我們看,我們的bugfix是不是就變得事半功倍了呢?
一、什么是鏈路追蹤?
鏈路追蹤(Distributed Tracing)是一種用于監測和診斷分布式應用程序中請求路徑的技術。在分布式系統中,單個請求可能會涉及多個服務和組件。鏈路追蹤通過記錄和分析請求在這些服務之間的傳遞路徑和執行情況,幫助開發人員和運維團隊理解系統的運行狀況、性能和問題。
二、鏈路追蹤是怎么實現的?
1.鏈路追蹤關鍵概念介紹
- Span(片段): 在鏈路追蹤中,Span 是描述單個操作或事件的基本單元。一個請求被分解成一個或多個 Span,每個 Span 表示一個操作的開始和結束。例如,一個數據庫查詢、一個 HTTP 請求、一個函數調用等都可以作為一個 Span。
- Context(上下文): 在鏈路追蹤中,上下文是指跨越不同服務的信息傳遞。每個 Span 都關聯一個上下文,允許跟蹤系統將相關的 Span 連接起來,以顯示請求的完整路徑。
- Trace ID(追蹤標識): Trace ID 是整個請求路徑的唯一標識符。它用于將整個請求的所有 Span 關聯到同一個 Trace 中。當一個請求進入系統時,生成一個唯一的 Trace ID,并在整個請求過程中一直保持不變,以確保所有的 Span 都能夠關聯到同一個 Trace 中。
- Span ID(Span 標識): Span ID 是用于標識單個操作或事件的唯一標識符。每個 Span 都有自己的 Span ID,它用于在 Trace 中標識不同的操作或事件。
2.span是怎么基于context進行關聯的?
由上面的概念我們大概可以想象到,一條追蹤鏈路其實是由多個span組成的,而span之間是基于每一個span的context進行關聯 (即根據context里的同一個trace id進行關聯)
三、OpenTelemetry、Jaeger這些和鏈路追蹤有什么關系?
- OpenTelemetry 是一個用于跟蹤和監控分布式系統的開放式標準和工具集。它提供了一套標準的API 和工具,用于生成、導出和聚合跟蹤數據,并將這些數據發送到各種后端,如 Jaeger、Zipkin、Prometheus 等。
- Jaeger這些系統為鏈路追蹤提供了一種可視化和分析分布式系統的能力,通過記錄請求的執行路徑和操作(span),在一個直觀的用戶界面中展示整個系統中的請求傳播路徑和性能數據。
四、怎么快速使用OpenTelemetry、Jaeger實現一個鏈路追蹤的demo
- 步驟1:需要安裝Jaeger,并運行Jaeger。Jaeger官方入門文檔
為了快速演示,我們可以使用官方推薦的測試方式用docker快速啟動:
然后,打開http://localhost:16686就可以訪問 Jaeger UI了。docker run --rm --name jaeger \-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \-p 6831:6831/udp \-p 6832:6832/udp \-p 5778:5778 \-p 16686:16686 \-p 4317:4317 \-p 4318:4318 \-p 14250:14250 \-p 14268:14268 \-p 14269:14269 \-p 9411:9411 \jaegertracing/all-in-one:1.51
- 步驟2:運行下面代碼,具體代碼請拉取我github上的demo
package mainimport ("context""fmt""log""net/http""go.opentelemetry.io/otel"`go.opentelemetry.io/otel/attribute`"go.opentelemetry.io/otel/exporters/trace/jaeger"`go.opentelemetry.io/otel/sdk/resource`sdktrace "go.opentelemetry.io/otel/sdk/trace"`go.opentelemetry.io/otel/semconv`svc `otel/demo1/svc`
)// 初始化 OpenTelemetry
func initTracer() *sdktrace.TracerProvider {exporter, err := jaeger.NewRawExporter(jaeger.WithAgentEndpoint(func(options *jaeger.AgentEndpointOptions) {options.Host = "localhost"options.Port = "6831"}),)if err != nil {log.Fatalf("Error creating Jaeger exporter: %v", err)}tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter),sdktrace.WithSampler(sdktrace.AlwaysSample()),sdktrace.WithResource(resource.NewWithAttributes(semconv.ServiceNameKey.String("demo_service"), // 服務名)),)otel.SetTracerProvider(tp)return tp
}func main() {tp := initTracer()defer func() {if cerr := tp.Shutdown(context.Background()); cerr != nil {log.Fatalf("Error shutting down tracer provider: %v", cerr)}}()//啟動http服務器http.HandleFunc("/demo", handleRequest)go func() {if err := http.ListenAndServe(":8080", nil); err != nil {log.Fatalf("Error starting Service A server: %v", err)}}()//模擬請求SimulateRequest()
}func handleRequest(w http.ResponseWriter, req *http.Request) {tracer := otel.Tracer("root")//開始創建root spanctx, span := tracer.Start(req.Context(), "span root")defer span.End()//可以在span上記錄一些信息,例如日志、請求參數、sql語句等span.SetAttributes(attribute.String("some root service info", "This is the root service"),)//訪問服務Asvc.CallServiceA(ctx)//訪問服務Bsvc.CallServiceB(ctx)w.WriteHeader(http.StatusOK)fmt.Fprintf(w, "Response from Service Root")
}func SimulateRequest() {req, err := http.NewRequest("GET", "http://localhost:8080/demo", nil)if err != nil {log.Fatalf("Creating request fail: %v", err)}resp, err := http.DefaultClient.Do(req)if err != nil {log.Fatalf("Request failed: %v", err)}defer resp.Body.Close()fmt.Println("Response received from Root Service")
}
運行后打開http://localhost:16686,選擇對應的service查找trace可以看到
五、總結
- 鏈路追蹤是依靠于一個隨機生成的trace_id,一條鏈路對應唯一一個trace_id。
- Span 是描述單個操作或事件的基本單元。一個請求被分解成一個或多個 Span。即一條鏈路是由多個span組成的。
- 在鏈路追蹤中,context(上下文)是指跨越不同服務的信息傳遞。每個 Span 都關聯一個上下文。
- OpenTelemetry 是一個用于跟蹤和監控分布式系統的開放式標準和工具集。提供了一套標準的API 和工具,用于生成、導出和聚合跟蹤數據,并將這些數據發送到各種后端。
- Jaeger、Zipkin、Prometheus等這些可以接收OpenTelemetry發送過來的數據,可以提供可視化的展示和分析數據的能力。