golang使用OpenTelemetry實(shí)現(xiàn)跨服務(wù)全鏈路追蹤詳解
使用 OpenTelemetry 鏈路追蹤說明
- 工作中常常會遇到需要查看服務(wù)調(diào)用關(guān)系,比如用戶請求了一個(gè)接口
- 接口會調(diào)用其他grpc,http接口,或者內(nèi)部的方法
- 這樣的調(diào)用鏈路,如果出現(xiàn)了問題,我們需要快速的定位問題,這時(shí)候就需要一個(gè)工具來幫助我們查看調(diào)用鏈路
- OpenTelemetry就是這樣一個(gè)工具
- 本文大概以:main 函數(shù)初始化 OpenTelemetry、啟動 http server、配置httpclient 請求服務(wù) 來進(jìn)行說明
- 完整可執(zhí)行源碼在:opentelemetry-go 示例
- 示例代碼已增加 grpc的鏈路追蹤
服務(wù)鏈路關(guān)系
關(guān)系圖
說明:
- 用戶 請求 api1(echo server) 服務(wù)的 api1/bar
- api1 調(diào)用 Grpc 服務(wù)
- api1 調(diào)用 api2 (gin server) 服務(wù)的 api2/bar
- api2 調(diào)用 api3 (echo server )服務(wù)的 api3/bar
- api3 調(diào)用 內(nèi)部 調(diào)用方法 bar->bar2->bar3
安裝jaeger
- 下載jaeger:我使用的是 jaeger-all-in-one
- 啟動 jaeger: ~/tool/jaeger-1.31.0-linux-amd64/jaeger-all-in-one
- 默認(rèn)查看面板 地址 http://localhost:16686/
- tracer Batcher的地址,下面代碼會體現(xiàn): http://localhost:14268/api/traces
初始化 全局的 OpenTelemetry
這里openTelemetry 的exporter 以 jaeger 為例
var tracer = otel.Tracer("go-moda") func InitJaegerProvider(jaegerUrl string, serviceName string) (func(ctx context.Context) error, error) { if jaegerUrl == "" { logger.Errorw("jaeger url is empty") return nil, nil } tracer = otel.Tracer(serviceName) exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerUrl))) if err != nil { return nil, err } tp := tracesdk.NewTracerProvider( tracesdk.WithBatcher(exp), tracesdk.WithResource(resource.NewSchemaless( semconv.ServiceNameKey.String(serviceName), )), ) otel.SetTracerProvider(tp) // otel.SetTextMapPropagator(propagation.TraceContext{}) b3Propagator := b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)) propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, b3Propagator) otel.SetTextMapPropagator(propagator) return tp.Shutdown, nil }
說明
- jaegerUrl ,如果安裝的是 jaeger-all-in-one,則地址默認(rèn)為 http://localhost:14268/api/traces
- serviceName 是服務(wù)名稱,這里我使用的是 api1,api2,api3
- 增加 span 可以使用 tracer.Start(ctx, "spanName")
http服務(wù)鏈路追蹤
初始化了全局的 OpenTelemetry后,在當(dāng)前服務(wù)就可以使用 OpenTelemetry 的 tracer 進(jìn)行鏈路追蹤 比如
ctx, span := tracing.Start(ctx, "service.bar") defer span.End()
但如果是跨服務(wù)進(jìn)行調(diào)用,比如 http server之間的調(diào)用,需要:
- 對于 http client: 請求server的時(shí)候,將ctx(上下文) 注入到 請求頭中(req header) 中
- 對于 http server: 在獲取http請求時(shí),解析 出請求頭 中的 parent trace 信息 這樣就可以實(shí)現(xiàn)跨服務(wù)鏈路追蹤
啟動 http服務(wù)開啟鏈路追蹤
http服務(wù),解析請求頭中的trace信息:echo 和 gin 都有成熟的的中間件,我們在初始化的時(shí)候,將中間件加入到服務(wù)中即可,下面是 echo 和 gin啟動服務(wù)的演示:
echo server 示例
import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" e := echo.New() e.Server.Use(otelecho.Middleware("moda"))
gin 舉例
import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ginEngine := gin.Default() g.GetServer().Use(otelgin.Middleware("my-server"))
http client 鏈路追蹤
httpserver 啟動時(shí) 通過解析 請求頭 中的 parent trace 來進(jìn)行鏈路追蹤
那么在調(diào)用服務(wù)時(shí),就需要將上下文注入到 req header 中 下面是我個(gè)人封裝的 httpclient,可以參考:
package tracing import ( "bytes" "context" "encoding/json" "io" "io/ioutil" "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // 新增 options http.Transport type ClientOption struct { Transport *http.Transport } type ClientOptionFunc func(*ClientOption) func WithClientTransport(transport *http.Transport) ClientOptionFunc { return func(option *ClientOption) { option.Transport = transport } } // CallAPI 為 http client 封裝,默認(rèn)使用 otelhttp.NewTransport(http.DefaultTransport) func CallAPI(ctx context.Context, url string, method string, reqBody interface{}, option ...ClientOptionFunc) ([]byte, error) { clientOption := &ClientOption{} for _, o := range option { o(clientOption) } client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} if clientOption.Transport != nil { client.Transport = otelhttp.NewTransport(clientOption.Transport) } var requestBody io.Reader if reqBody != nil { payload, err := json.Marshal(reqBody) if err != nil { return nil, err } requestBody = bytes.NewReader(payload) } req, err := http.NewRequestWithContext(ctx, method, url, requestBody) if err != nil { return nil, err } resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() resBody, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return resBody, nil }
說明
- 上面代碼中,主要是使用了 otelhttp.NewTransport(http.DefaultTransport) 將上下文注入到 req header 中
- 調(diào)用服務(wù)時(shí),需要將上下文(ctx)傳入到 CallAPI 方法
調(diào)用服務(wù),查看鏈路關(guān)系
實(shí)戰(zhàn)代碼演示
跨服務(wù) 鏈路追蹤 大概說完 下面是運(yùn)行實(shí)戰(zhàn)代碼,分為普通運(yùn)行和docker 一鍵運(yùn)行
查看源碼位置:opentelemetry-go 示例
普通運(yùn)行
- 示例文件:moda_tracing下 有四個(gè)目錄,分別是 api1_http,api2_http,api3_http,grpc 分別對應(yīng)三個(gè)api服務(wù) 一個(gè)grpc服務(wù)
- 分別啟動三個(gè)服務(wù),進(jìn)入目錄 go run ./ -c ./conf.toml 即可啟動服務(wù)
docker 運(yùn)行
- 進(jìn)入moda_tracing目錄
- 執(zhí)行 make deploy,會同時(shí)啟動 jaeger,api1,api2,api3,grpc(mac 和 linux經(jīng)過試驗(yàn)可行,win如不行可使用第一種)
查看jaeger 鏈路
- 根據(jù)上面鏈路關(guān)系,調(diào)用api1 等待調(diào)用完成: curl localhost:8081/api1/bar
- 打開 jaeger 面板,查看鏈路關(guān)系圖,http://localhost:16686/
可以看到對應(yīng)的鏈路,在bar,bar2,bar3 刻意sleep 加了耗時(shí)也體現(xiàn)了出來
以上就是golang使用OpenTelemetry實(shí)現(xiàn)跨服務(wù)全鏈路追蹤詳解的詳細(xì)內(nèi)容,更多關(guān)于go OpenTelemetry全鏈路追蹤的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gin框架Context如何獲取Get?Query?Param函數(shù)數(shù)據(jù)
這篇文章主要為大家介紹了gin框架Context?Get?Query?Param函數(shù)獲取數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03使用go進(jìn)行云存儲上傳實(shí)現(xiàn)實(shí)例
這篇文章主要為大家介紹了使用go進(jìn)行云存儲上傳實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2024-01-01Golang在整潔架構(gòu)基礎(chǔ)上實(shí)現(xiàn)事務(wù)操作
這篇文章在 go-kratos 官方的 layout 項(xiàng)目的整潔架構(gòu)基礎(chǔ)上,實(shí)現(xiàn)優(yōu)雅的數(shù)據(jù)庫事務(wù)操作,需要的朋友可以參考下2024-08-08Golang 關(guān)于Gin框架請求參數(shù)的獲取方法
Gin是Go語言的Web框架,提供路由和中間件支持,本文介紹如何使用Gin獲取HTTP請求參數(shù),包括URLPath參數(shù)、URLQuery參數(shù)、HTTPBody參數(shù)和Header參數(shù),詳解直接獲取和綁定到結(jié)構(gòu)體兩種方法,幫助開發(fā)者高效處理Web請求2024-10-10Hugo?Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析
這篇文章主要為大家介紹了Hugo?Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02