溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

go-zero中怎么追蹤請求鏈路的

發(fā)布時間:2021-07-24 13:50:34 來源:億速云 閱讀:217 作者:Leah 欄目:編程語言

這篇文章給大家介紹go-zero中怎么追蹤請求鏈路的,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

go-zero 是如何追蹤你的請求鏈路

微服務(wù)架構(gòu)中,調(diào)用鏈可能很漫長,從 httprpc ,又從 rpchttp 。而開發(fā)者想了解每個環(huán)節(jié)的調(diào)用情況及性能,最佳方案就是 全鏈路跟蹤

追蹤的方法就是在一個請求開始時生成一個自己的 spanID ,隨著整個請求鏈路傳下去。我們則通過這個 spanID 查看整個鏈路的情況和性能問題。

下面來看看 go-zero 的鏈路實(shí)現(xiàn)。

代碼結(jié)構(gòu)

  • spancontext:保存鏈路的上下文信息「traceid,spanid,或者是其他想要傳遞的內(nèi)容」

  • span:鏈路中的一個操作,存儲時間和某些信息

  • propagator: trace 傳播下游的操作「抽取,注入」

  • noop:實(shí)現(xiàn)了空的 tracer 實(shí)現(xiàn)

go-zero中怎么追蹤請求鏈路的

概念

SpanContext

在介紹 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
}

Span

一個 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ù)。

實(shí)例應(yīng)用

go-zero 中http,rpc中已經(jīng)作為內(nèi)置中間件集成。我們以 http,rpc 中,看看 tracing 是怎么使用的:

HTTP

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,
    }
}
  1. 將 header -> carrier,獲取 header 中的traceId等信息

  2. 開啟一個新的 span,并把**「traceId,spanId」**封裝在context中

  3. 從上述的 carrier「也就是header」獲取traceId,spanId。


    • 看header中是否設(shè)置


    • 如果沒有設(shè)置,則隨機(jī)生成返回

  4. request 中產(chǎn)生新的ctx,并將相應(yīng)的信息封裝在 ctx 中,返回

  5. 從上述的 context,拷貝一份到當(dāng)前的 request

go-zero中怎么追蹤請求鏈路的

這樣就實(shí)現(xiàn)了 span 的信息隨著 request 傳遞到下游服務(wù)。

RPC

在 rpc 中存在 client, server ,所以從 tracing 上也有 clientTracing, serverTracingserveTracing 的邏輯基本與 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
}
  1. 獲取上游帶下來的 span 上下文信息

  2. 從獲取的 span 中創(chuàng)建新的 ctx,span「繼承父span的traceId」

  3. 將生成 span 的data加入ctx,傳遞到下一個中間件,流至下游

總結(jié)

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é)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(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)容。

AI