分佈式鏈路追蹤續集
本文是 分佈式鏈路追蹤 的續集。
在上一文中提到爲了統一各種分佈式追蹤系統的實現,CNCF (雲原生計算基金會)下的 OpenTracing 項目定義了一套分佈式追蹤的標準,可以使用 OpenTracing API 完成代碼的監控埋點,最後用戶自由選擇遵循 OpenTracing 標準的鏈路追蹤系統,比如 jaeger 。
但是現在訪問 OpenTracing 的 官網 [1] ,可以發現官網提醒 OpenTracing 和 OpenCensus 已經被合併成爲 OpenTelemetry 。(在上文編寫時我未發現到,感謝 Donald Liu 大佬的提醒)
在 cncf[2] 上也可以發現 OpenTracing 已經被 Archived 了
相應地,OpenTelemetry 已經正式成爲了 CNCF 的孵化項目 參考 [3]
Both OpenTracing and OpenCensus will be further deprecated in the coming weeks, with OpenTracing being formally archived by the CNCF TOC.
緣由
參考 [4]
在 APM (Application Performance Monitoring) 領域,或者說微服務的可觀察性方面包括有分佈式鏈路追蹤,指標監控和日誌。
OpenTracing 是最早爲分佈式追蹤制定了一套平臺無關、廠商無關的協議標準的項目,並以此成爲了 CNCF 的孵化項目。
在之後,谷歌牽頭,微軟加入,創建了 OpenCensus 項目統一 Metrics 基礎指標監控的使用方式,還做了 OpenTracing 的老本行:分佈式追蹤。
一山不容二虎,OpenTracing 和 OpenCensus 愈打愈烈,對我們用戶來講,實在是太不友好了。
然後 OpenTelemetry 橫空出世了,OpenTracing 和 OpenCensus 既然都這麼好,乾脆你們合併起來吧,我 OpenTelemetry 來兼容你們。
OpenTelemetry 的自身定位十分明確:數據採集和標準規範的統一,對於數據如何去使用、存儲、展示、告警,官方是不涉及的。
OpenTelemetry 的終極目標十分偉大:實現 Metrics、Tracing、Logging 的融合及大一統,作爲 APM 的數據採集終極解決方案。
然後就是現在的故事了,OpenTelemetry 正式成爲 CNCF 的孵化項目,OpenTracing 和 OpenCensus 不再維護。
兼容性
這個放心,大部分的知識點定義還是一致的 (如數據模型:Trace, Span, SpanContext) ,但是 API 的使用會有所區別,詳細可以查看 OpenTelemetry 規範文檔 [5]
承接上文
我們現在對上文的 OpenTracing API 的代碼做出修改,擁抱 OpenTelemetry 吧!
安裝 OpenTelemetry for Go 依賴
go get go.opentelemetry.io/otel@v1.0.0-RC1 go.opentelemetry.io/otel/sdk@v1.0.0-RC1 go.opentelemetry.io/otel/exporters/stdout/stdouttrace@v1.0.0-RC1 go.opentelemetry.io/otel/trace@v1.0.0-RC1
安裝 OpenTelemetry for jaeger 依賴
go get -u go.opentelemetry.io/otel/exporters/jaeger
OpenTelemetry 官方爲多種開源框架提供了開箱即用的 Instrumentation Packages ,如 gin , beego , mux , go-kit 等,當然也支持 net/http 標準庫 ,更多可瀏覽 opentelemetry-go-contrib[6]
我們使用的是 net/http 標準庫,所以直接導入 otelhttp
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
main.go
修改如下:
package main
import (
"fmt"
"log"
"math/rand"
"net/http"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
func init() {
exp, err := jaeger.New(jaeger.WithAgentEndpoint())
if err != nil {
panic(err)
}
tp := tracesdk.NewTracerProvider(
tracesdk.WithSampler(tracesdk.AlwaysSample()),
tracesdk.WithBatcher(exp),
tracesdk.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("opentelemetry-example"), // 服務名
semconv.ServiceVersionKey.String("0.0.1"),
)),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
}
func main() {
port := 8080
addr := fmt.Sprintf(":%d", port)
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
mux.Handle("/home", otelhttp.NewHandler(http.HandlerFunc(homeHandler), "請求 /home"))
mux.Handle("/async", otelhttp.NewHandler(http.HandlerFunc(serviceHandler), "請求 /async"))
mux.Handle("/service", otelhttp.NewHandler(http.HandlerFunc(serviceHandler), "請求 /service"))
mux.Handle("/db", otelhttp.NewHandler(http.HandlerFunc(dbHandler), "請求 /db"))
fmt.Printf("http://localhost:%d\n", port)
log.Fatal(http.ListenAndServe(addr, mux))
}
// 主頁 Html
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<a href="/home"> 點擊開始發起請求 </a>`))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("開始請求...\n"))
ctx := r.Context()
span := trace.SpanFromContext(ctx)
defer span.End()
// 發起異步請求
asyncReq, _ := http.NewRequest("GET", "http://localhost:8080/async", nil)
// 傳遞span的上下文信息
// 將關於本地追蹤調用的span context,設置到http header上,並傳遞出去
otel.GetTextMapPropagator().Inject(ctx,
propagation.HeaderCarrier(asyncReq.Header),
)
go func() {
if _, err := http.DefaultClient.Do(asyncReq); err != nil {
span.RecordError(err)
span.SetAttributes(
attribute.String("請求 /async error", err.Error()),
)
}
}()
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
// 發起同步請求
syncReq, _ := http.NewRequest("GET", "http://localhost:8080/service", nil)
otel.GetTextMapPropagator().Inject(ctx,
propagation.HeaderCarrier(syncReq.Header),
)
if _, err := http.DefaultClient.Do(syncReq); err != nil {
span.RecordError(err)
span.SetAttributes(
attribute.String("請求 /service error", err.Error()),
)
}
w.Write([]byte("請求結束!"))
}
// 模擬業務請求
func serviceHandler(w http.ResponseWriter, r *http.Request) {
// 通過http header,提取span元數據信息
span := trace.SpanFromContext(
otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)),
)
defer span.End()
dbReq, _ := http.NewRequest("GET", "http://localhost:8080/db", nil)
otel.GetTextMapPropagator().Inject(r.Context(), propagation.HeaderCarrier(dbReq.Header))
if _, err := http.DefaultClient.Do(dbReq); err != nil {
span.RecordError(err)
span.SetAttributes(
attribute.String("請求 /db error", err.Error()),
)
}
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
}
// 模擬DB調用
func dbHandler(w http.ResponseWriter, r *http.Request) {
// 通過http header,提取span元數據信息
span := trace.SpanFromContext(
otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)),
)
defer span.End()
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
}
同樣的部署到集羣中
kubectl apply -f https://raw.githubusercontent.com/togettoyou/jaeger-example/master/jaeger-example-opentelemetry.yaml -n observability
訪問後觀察 Jaeger UI 可以發現調用鏈和之前的也一致,包含 5 個 Span
具體 Span 信息
[1]
官網: https://opentracing.io/
[2]
cncf: https://www.cncf.io/archived-projects/
[3]
參考: https://www.cncf.io/blog/2021/08/26/opentelemetry-becomes-a-cncf-incubating-project/
[4]
參考: https://github.com/open-telemetry/docs-cn/blob/main/OT.md
[5]
OpenTelemetry 規範文檔: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md
[6]
opentelemetry-go-contrib: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/README.md
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lriEqTo6-f8Kn1yXeGyP5Q