您好,登錄后才能下訂單哦!
這篇文章給大家介紹go-zero中怎么追蹤請求鏈路的,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
微服務(wù)架構(gòu)中,調(diào)用鏈可能很漫長,從 http
到 rpc
,又從 rpc
到 http
。而開發(fā)者想了解每個環(huán)節(jié)的調(diào)用情況及性能,最佳方案就是 全鏈路跟蹤。
追蹤的方法就是在一個請求開始時生成一個自己的 spanID
,隨著整個請求鏈路傳下去。我們則通過這個 spanID
查看整個鏈路的情況和性能問題。
下面來看看 go-zero
的鏈路實(shí)現(xiàn)。
spancontext:保存鏈路的上下文信息「traceid,spanid,或者是其他想要傳遞的內(nèi)容」
span:鏈路中的一個操作,存儲時間和某些信息
propagator: trace
傳播下游的操作「抽取,注入」
noop:實(shí)現(xiàn)了空的 tracer
實(shí)現(xiàn)
在介紹 span
之前,先引入 context
。SpanContext 保存了分布式追蹤的上下文信息,包括 Trace id,Span id 以及其它需要傳遞到下游的內(nèi)容。OpenTracing 的實(shí)現(xiàn)需要將 SpanContext 通過某種協(xié)議 進(jìn)行傳遞,以將不同進(jìn)程中的 Span 關(guān)聯(lián)到同一個 Trace 上。對于 HTTP 請求來說,SpanContext 一般是采用 HTTP header 進(jìn)行傳遞的。
下面是 go-zero
默認(rèn)實(shí)現(xiàn)的 spanContext
type spanContext struct { traceId string // TraceID 表示tracer的全局唯一ID spanId string // SpanId 標(biāo)示單個trace中某一個span的唯一ID,在trace中唯一 }
同時開發(fā)者也可以實(shí)現(xiàn) SpanContext
提供的接口方法,實(shí)現(xiàn)自己的上下文信息傳遞:
type SpanContext interface { TraceId() string // get TraceId SpanId() string // get SpanId Visit(fn func(key, val string) bool) // 自定義操作TraceId,SpanId }
一個 REST 調(diào)用或者數(shù)據(jù)庫操作等,都可以作為一個 span
。 span
是分布式追蹤的最小跟蹤單位,一個 Trace 由多段 Span 組成。追蹤信息包含如下信息:
type Span struct { ctx spanContext // 傳遞的上下文 serviceName string // 服務(wù)名 operationName string // 操作 startTime time.Time // 開始時間戳 flag string // 標(biāo)記開啟trace是 server 還是 client children int // 本 span fork出來的 childsnums }
從 span
的定義結(jié)構(gòu)來看:在微服務(wù)中, 這就是一個完整的子調(diào)用過程,有調(diào)用開始 startTime
,有標(biāo)記自己唯一屬性的上下文結(jié)構(gòu) spanContext
以及 fork 的子節(jié)點(diǎn)數(shù)。
在 go-zero
中http,rpc中已經(jīng)作為內(nèi)置中間件集成。我們以 http,rpc 中,看看 tracing
是怎么使用的:
func TracingHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // **1** carrier, err := trace.Extract(trace.HttpFormat, r.Header) // ErrInvalidCarrier means no trace id was set in http header if err != nil && err != trace.ErrInvalidCarrier { logx.Error(err) } // **2** ctx, span := trace.StartServerSpan(r.Context(), carrier, sysx.Hostname(), r.RequestURI) defer span.Finish() // **5** r = r.WithContext(ctx) next.ServeHTTP(w, r) }) } func StartServerSpan(ctx context.Context, carrier Carrier, serviceName, operationName string) ( context.Context, tracespec.Trace) { span := newServerSpan(carrier, serviceName, operationName) // **4** return context.WithValue(ctx, tracespec.TracingKey, span), span } func newServerSpan(carrier Carrier, serviceName, operationName string) tracespec.Trace { // **3** traceId := stringx.TakeWithPriority(func() string { if carrier != nil { return carrier.Get(traceIdKey) } return "" }, func() string { return stringx.RandId() }) spanId := stringx.TakeWithPriority(func() string { if carrier != nil { return carrier.Get(spanIdKey) } return "" }, func() string { return initSpanId }) return &Span{ ctx: spanContext{ traceId: traceId, spanId: spanId, }, serviceName: serviceName, operationName: operationName, startTime: timex.Time(), // 標(biāo)記為server flag: serverFlag, } }
將 header -> carrier,獲取 header 中的traceId等信息
開啟一個新的 span,并把**「traceId,spanId」**封裝在context中
從上述的 carrier「也就是header」獲取traceId,spanId。
看header中是否設(shè)置
如果沒有設(shè)置,則隨機(jī)生成返回
從 request
中產(chǎn)生新的ctx,并將相應(yīng)的信息封裝在 ctx 中,返回
從上述的 context,拷貝一份到當(dāng)前的 request
這樣就實(shí)現(xiàn)了 span
的信息隨著 request
傳遞到下游服務(wù)。
在 rpc 中存在 client, server
,所以從 tracing
上也有 clientTracing, serverTracing
。 serveTracing
的邏輯基本與 http 的一致,來看看 clientTracing
是怎么使用的?
func TracingInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { // open clientSpan ctx, span := trace.StartClientSpan(ctx, cc.Target(), method) defer span.Finish() var pairs []string span.Visit(func(key, val string) bool { pairs = append(pairs, key, val) return true }) // **3** 將 pair 中的data以map的形式加入 ctx ctx = metadata.AppendToOutgoingContext(ctx, pairs...) return invoker(ctx, method, req, reply, cc, opts...) } func StartClientSpan(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) { // **1** if span, ok := ctx.Value(tracespec.TracingKey).(*Span); ok { // **2** return span.Fork(ctx, serviceName, operationName) } return ctx, emptyNoopSpan }
獲取上游帶下來的 span 上下文信息
從獲取的 span 中創(chuàng)建新的 ctx,span「繼承父span的traceId」
將生成 span 的data加入ctx,傳遞到下一個中間件,流至下游
go-zero
通過攔截請求獲取鏈路traceID,然后在中間件函數(shù)入口會分配一個根Span,然后在后續(xù)操作中會分裂出子Span,每個span都有自己的具體的標(biāo)識,F(xiàn)insh之后就會匯集在鏈路追蹤系統(tǒng)中。
開發(fā)者可以通過 ELK
工具追蹤 traceID
,看到整個調(diào)用鏈。同時 go-zero
并沒有提供整套 trace
鏈路方案,開發(fā)者可以封裝 go-zero
已有的 span
結(jié)構(gòu),做自己的上報系統(tǒng),接入 jaeger, zipkin
等鏈路追蹤工具。
關(guān)于go-zero中怎么追蹤請求鏈路的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。