Go微服務(wù)全鏈路跟蹤詳解

在微服務(wù)架構(gòu)中鼠渺,調(diào)用鏈?zhǔn)锹L而復(fù)雜的鸭巴,要了解其中的每個環(huán)節(jié)及其性能,你需要全鏈路跟蹤拦盹。它的原理很簡單鹃祖,你可以在每個請求開始時生成一個唯一的 ID,并將其傳遞到整個調(diào)用鏈普舆。該 ID 稱為 CorrelationID1恬口,你可以用它來跟蹤整個請求并獲得各個調(diào)用環(huán)節(jié)的性能指標(biāo)。簡單來說有兩個問題需要解決沼侣。第一祖能,如何在應(yīng)用程序內(nèi)部傳遞 ID; 第二,當(dāng)你需要調(diào)用另一個微服務(wù)時蛾洛,如何通過網(wǎng)絡(luò)傳遞 ID养铸。

什么是 OpenTracing?

現(xiàn)在有許多開源的分布式跟蹤庫可供選擇雁芙,其中最受歡迎的庫可能是 Zipkin2 和Jaeger3。選擇哪個是一個令人頭疼的問題钞螟,因為你現(xiàn)在可以選擇最受歡迎的一個兔甘,但是如果以后有一個更好的出現(xiàn)呢?OpenTracing? 可以幫你解決這個問題鳞滨。它建立了一套跟蹤庫的通用接口洞焙,這樣你的程序只需要調(diào)用這些接口而不被具體的跟蹤庫綁定,將來可以切換到不同的跟蹤庫而無需更改代碼拯啦。Zipkin 和Jaeger 都支持 OpenTracing澡匪。

如何跟蹤服務(wù)器端點 (server endpoints)?

在下面的程序中我使用 “Zipkin” 作為跟蹤庫,用 “OpenTracing” 作為通用跟蹤接口褒链。跟蹤系統(tǒng)中通常有四個組件仙蛉,下面我用 Zipkin 作為示例:

  • recorder (記錄器):記錄跟蹤數(shù)據(jù)

  • Reporter (or collecting agent) (報告器或收集代理):從記錄器收集數(shù)據(jù)并將數(shù)據(jù)發(fā)送到 UI 程序

  • Tracer:生成跟蹤數(shù)據(jù)

  • UI:負(fù)責(zé)在圖形 UI 中顯示跟蹤數(shù)據(jù)

image

上面是 Zipkin 的組件圖,你可以在 Zipkin Architecture (https://zipkin.io/pages/architecture.html)中找到它碱蒙。

有兩種不同類型的跟蹤荠瘪,一種是進(jìn)程內(nèi)跟蹤(in-process),另一種是跨進(jìn)程跟蹤(cross-process)赛惩。我們將首先討論跨進(jìn)程跟蹤哀墓。

客戶端程序:

我們將用一個簡單的 gRPC 程序作為示例,它分成客戶端和服務(wù)器端代碼喷兼。我們想跟蹤一個完整的服務(wù)請求篮绰,它從客戶端到服務(wù)端并從服務(wù)端返回。以下是在客戶端創(chuàng)建新跟蹤器的代碼季惯。它首先創(chuàng)建 “HTTP Collector”(the agent) 用來收集跟蹤數(shù)據(jù)并將其發(fā)送到 “Zipkin” UI吠各, “endpointUrl” 是 “Zipkin” UI 的 URL。其次勉抓,它創(chuàng)建了一個記錄器(recorder)來記錄端點上的信息贾漏,“hostUrl” 是 gRPC (客戶端)呼叫的 URL。第三藕筋,它用我們新建的記錄器創(chuàng)建了一個新的跟蹤器(tracer)纵散。最后,它為 “OpenTracing” 設(shè)置了 “GlobalTracer”隐圾,這樣你可以在程序中的任何地方訪問它伍掀。

const (
    endpoint_url = "http://localhost:9411/api/v1/spans"
    host_url = "localhost:5051"
    service_name_cache_client = "cache service client"
    service_name_call_get = "callGet"
)

func newTracer () (opentracing.Tracer, zipkintracer.Collector, error) {
    collector, err := openzipkin.NewHTTPCollector(endpoint_url)
    if err != nil {
        return nil, nil, err
    }
    recorder :=openzipkin.NewRecorder(collector, true, host_url, service_name_cache_client)
    tracer, err := openzipkin.NewTracer(
        recorder,
        openzipkin.ClientServerSameSpan(true))

    if err != nil {
        return nil,nil,err
    }
    opentracing.SetGlobalTracer(tracer)

    return tracer,collector, nil
}

以下是 gRPC 客戶端代碼。它首先調(diào)用上面提到的函數(shù) “newTrace()” 來創(chuàng)建跟蹤器暇藏,然后蜜笤,它創(chuàng)建一個包含跟蹤器的 gRPC 調(diào)用連接。接下來盐碱,它使用新建的gRPC 連接創(chuàng)建緩存服務(wù) (Cache service) 的 gRPC 客戶端把兔。最后啊胶,它通過 gRPC 客戶端來調(diào)用緩存服務(wù)的 “Get” 函數(shù)。

key:="123"
    tracer, collector, err :=newTracer()
    if err != nil {
        panic(err)
    }
    defer collector.Close()
    connection, err := grpc.Dial(host_url,
        grpc.WithInsecure(), grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads())),
        )
    if err != nil {
        panic(err)
    }
    defer connection.Close()
    client := pb.NewCacheServiceClient(connection)
    value, err := callGet(key, client)

Trace 和 Span:

在 OpenTracing 中垛贤,一個重要的概念是 “trace”焰坪,它表示從頭到尾的一個請求的調(diào)用鏈,它的標(biāo)識符是 “traceID”聘惦。一個 “trace” 包含有許多跨度 (span)某饰,每個跨度捕獲調(diào)用鏈內(nèi)的一個工作單元,并由 “spanId” 標(biāo)識善绎。每個跨度具有一個父跨度黔漂,并且一個 “trace” 的所有跨度形成有向無環(huán)圖 (DAG)。以下是跨度之間的關(guān)系圖禀酱。你可以從 The OpenTracing Semantic Specification

https://opentracing.io/specification/) 中找到它炬守。

image

以下是函數(shù) “callGet” 的代碼,它調(diào)用了 gRPC 服務(wù)端的 “Get" 函數(shù)剂跟。在函數(shù)的開頭减途,OpenTracing 為這個函數(shù)調(diào)用開啟了一個新的 span,整個函數(shù)結(jié)束后曹洽,它也結(jié)束了這個 span鳍置。


const service_name_call_get = "callGet"

func callGet(key string, c pb.CacheServiceClient) ( []byte, error) {
    span := opentracing.StartSpan(service_name_call_get)
    defer span.Finish()
    time.Sleep(5*time.Millisecond)
    // Put root span in context so it will be used in our calls to the client.
    ctx := opentracing.ContextWithSpan(context.Background(), span)
    //ctx := context.Background()
    getReq:=&pb.GetReq{Key:key}
    getResp, err :=c.Get(ctx, getReq )
    value := getResp.Value
    return value, err
}

服務(wù)端代碼:

下面是服務(wù)端代碼,它與客戶端代碼類似送淆,它調(diào)用了 “newTracer()” (與客戶端“newTracer()”函數(shù)幾乎相同)來創(chuàng)建跟蹤器税产。然后,它創(chuàng)建了一個 “OpenTracingServerInterceptor”偷崩,其中包含跟蹤器辟拷。最后,它使用我們剛創(chuàng)建的攔截器 (Interceptor) 創(chuàng)建了 gRPC 服務(wù)器阐斜。

connection, err := net.Listen(network, host_url)
    if err != nil {
        panic(err)
    }
    tracer,err  := newTracer()
    if err != nil {
        panic(err)
    }
    opts := []grpc.ServerOption{
        grpc.UnaryInterceptor(
            otgrpc.OpenTracingServerInterceptor(tracer,otgrpc.LogPayloads()),
        ),
    }
    srv := grpc.NewServer(opts...)
    cs := initCache()
    pb.RegisterCacheServiceServer(srv, cs)

    err = srv.Serve(connection)
    if err != nil {
        panic(err)
    } else {
        fmt.Println("server listening on port 5051")
    }

以下是運行上述代碼后在 Zipkin 中看到的跟蹤和跨度的圖片衫冻。在服務(wù)器端,我們不需要在函數(shù)內(nèi)部編寫任何代碼來生成 span智听,我們需要做的就是創(chuàng)建跟蹤器(tracer)羽杰,服務(wù)器攔截器自動為我們生成span渡紫。

image

怎樣跟蹤函數(shù)內(nèi)部?

上面的圖片沒有告訴我們函數(shù)內(nèi)部的跟蹤細(xì)節(jié)到推, 我們需要編寫一些代碼來獲得它。

以下是服務(wù)器端 “get” 函數(shù)惕澎,我們在其中添加了跟蹤代碼莉测。它首先從上下文獲取跨度(span),然后創(chuàng)建一個新的子跨度并使用我們剛剛獲得的跨度作為父跨度唧喉。接下來捣卤,它執(zhí)行一些操作(例如數(shù)據(jù)庫查詢),然后結(jié)束 (mysqlSpan.Finish()) 子跨度。


const service_name_db_query_user = "db query user"

func (c *CacheService) Get(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) {
    time.Sleep(5*time.Millisecond)
    if parent := opentracing.SpanFromContext(ctx); parent != nil {
        pctx := parent.Context()
        if tracer := opentracing.GlobalTracer(); tracer != nil {
            mysqlSpan := tracer.StartSpan(service_name_db_query_user, opentracing.ChildOf(pctx))
            defer mysqlSpan.Finish()
            //do some operations
            time.Sleep(time.Millisecond * 10)
        }
    }
    key := req.GetKey()
    value := c.storage[key]
    fmt.Println("get called with return of value: ", value)
    resp := &pb.GetResp{Value: value}
    return resp, nil

}

以下是它運行后的圖片∥侍叮現(xiàn)在它在服務(wù)器端有一個新的跨度 “db query user”腥例。

image

以下是 zipkin 中的跟蹤數(shù)據(jù)。你可以看到客戶端從 8.016ms 開始子姜,服務(wù)端也在同一時間啟動祟绊。服務(wù)器端完成需要大約 16ms。

image

怎樣跟蹤數(shù)據(jù)庫?

怎樣才能跟蹤數(shù)據(jù)庫內(nèi)部的操作哥捕?首先牧抽,數(shù)據(jù)庫驅(qū)動程序需要支持跟蹤,另外你需要將跟蹤器 (tracer) 傳遞到數(shù)據(jù)庫函數(shù)中遥赚。如果數(shù)據(jù)庫驅(qū)動程序不支持跟蹤怎么辦扬舒?現(xiàn)在已經(jīng)有幾個開源驅(qū)動程序封裝器 (Wrapper),它們可以封裝任何數(shù)據(jù)庫驅(qū)動程序并使其支持跟蹤凫佛。其中一個是instrumentedsql? (另外兩個是 luna-duclos/instrumentedsql? 和 ocsql/driver.go?)讲坎。我簡要地看了一下他們的代碼,他們的原理基本相同愧薛。它們都為底層數(shù)據(jù)庫的每個函數(shù)創(chuàng)建了一個封裝 (Wrapper)衣赶,并在每個數(shù)據(jù)庫操作之前啟動一個新的跨度,并在操作完成后結(jié)束跨度厚满。但是所有這些都只封裝了 “database/sql” 接口府瞄,這就意味著 NoSQL 數(shù)據(jù)庫沒有辦法使用他們。如果你找不到支持你需要的 NoSQL 數(shù)據(jù)庫(例如 MongoDB) 的 OpenTracing 的驅(qū)動程序碘箍,你可能需要自己編寫一個封裝 (Wrapper) ,它并不困難遵馆。

一個問題是“如果我使用 OpenTracing 和 Zipkin 而數(shù)據(jù)庫驅(qū)動程序使用Openeracing 和 Jaeger,那會有問題嗎丰榴?"這其實不會發(fā)生货邓。我上面提到的大部分封裝都支持 OpenTracing。在使用封裝時四濒,你需要注冊封裝了的 SQL 驅(qū)動程序换况,其中包含跟蹤器。在 SQL 驅(qū)動程序內(nèi)部盗蟆,所有跟蹤函數(shù)都只調(diào)用了 OpenTracing 的接口戈二,因此它們甚至不知道底層實現(xiàn)是 Zipkin 還是 Jaeger。現(xiàn)在使用 OpenTarcing 的好處終于體現(xiàn)出來了喳资。在應(yīng)用程序中創(chuàng)建全局跟蹤器時(Global tracer)觉吭,你需要決定是使用 Zipkin 還是 Jaeger,但這之后仆邓,應(yīng)用程序或第三方庫中的每個函數(shù)都只調(diào)用 OpenTracing 接口鲜滩,已經(jīng)與具體的跟蹤庫 (Zipkin 或 Jaeger)沒關(guān)系了伴鳖。

怎樣跟蹤服務(wù)調(diào)用?

假設(shè)我們需要在 gRPC 服務(wù)中調(diào)用另外一個微服務(wù)(例如 RESTFul 服務(wù)),該如何跟蹤徙硅?

簡單來說就是使用 HTTP 頭作為媒介(Carrier)來傳遞跟蹤信息 (traceID)榜聂。無論微服務(wù)是 gRPC 還是 RESTFul,它們都使用 HTTP 協(xié)議嗓蘑。如果是消息隊列(Message Queue)峻汉,則將跟蹤信息 (traceID) 放入消息報頭中。(Zipkin B3-propogation 有 “single header” 和 “multiple header” 有兩種不同類型的跟蹤信息脐往,但 JMS 僅支持 “single header”)休吠。

一個重要的概念是“跟蹤上下文 (trace context)”,它定義了傳播跟蹤所需的所有信息业簿,例如 traceID瘤礁,parentId(父spanId) 等。有關(guān)詳細(xì)信息梅尤,請閱讀跟蹤上下文(trace context)1?(https://www.w3.org/TR/trace-context/)柜思。

OpenTracing提供了兩個處理“跟蹤上下文(trace context)”的函數(shù):“extract(format,carrier)”和“inject(SpanContext巷燥,format赡盘,carrier)”$志荆“extarct()”從媒介(通常是HTTP頭)獲取跟蹤上下文陨享。“inject” 將跟蹤上下文放入媒介钝腺,來保證跟蹤鏈的連續(xù)性抛姑。以下是我從Zipkin獲取的 b3-propagation(https://github.com/openzipkin/b3-propagation)圖。

image

但是為什么我們沒有在上面的例子中調(diào)用這些函數(shù)呢艳狐?讓我們再來回顧一下代碼定硝。在客戶端,在創(chuàng)建gRPC客戶端連接時毫目,我們調(diào)用了一個為“OpenTracingClientInterceptor”的函數(shù)蔬啡。以下是“OpenTracingClientInterceptor”的部分代碼,我從otgrpc11包中的 “client.go” 中得到了它镀虐。它已經(jīng)從 Go context12 獲取了跟蹤上下文并將其注入HTTP 頭箱蟆,因此我們不再需要再次調(diào)用 “inject” 函數(shù)。

func OpenTracingClientInterceptor(tracer opentracing.Tracer, optFuncs ...Option) 
  grpc.UnaryClientInterceptor {
    ...
    ctx = injectSpanContext(ctx, tracer, clientSpan)
    ...
  }

  func injectSpanContext(ctx context.Context, tracer opentracing.Tracer, clientSpan opentracing.Span) 
    context.Context {
      md, ok := metadata.FromOutgoingContext(ctx)
      if !ok {
        md = metadata.New(nil)
      } else {
        md = md.Copy()
      }
      mdWriter := metadataReaderWriter{md}
      err := tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, mdWriter)
      // We have no better place to record an error than the Span itself :-/
      if err != nil {
        clientSpan.LogFields(log.String("event", "Tracer.Inject() failed"), log.Error(err))
      }
      return metadata.NewOutgoingContext(ctx, md)
}

在服務(wù)器端粉私,我們還調(diào)用了一個函數(shù)“otgrpc.OpenTracingServerInterceptor”顽腾,其代碼類似于客戶端的 “OpenTracingClientInterceptor”。它不是調(diào)用 “inject” 寫入跟蹤上下文诺核,而是從 HTTP 頭中提瘸ぁ(extract)跟蹤上下文并將其放入 Go 上下文(Go context)中。這就是我們不需要再次手動調(diào)用 “extract()” 的原因窖杀。我們可以直接從 Go上下文中提取跟蹤上下文(opentracing.SpanFromContext(ctx))漓摩。但對于其他基于 HTTP 的服務(wù)(如 RESTFul 服務(wù)), 情況就并非如此入客,因此我們需要寫代碼從服務(wù)器端的 HTTP 頭中提取跟蹤上下文管毙。當(dāng)然,您也可以使用攔截器或過濾器桌硫。

跟蹤庫之間的互兼容性

你也許會問“如果我的程序使用 Zipkin 和 OpenTracing 而需要調(diào)用的第三方微服務(wù)使用 OpenTracing 與 Jaeger夭咬,它們會兼容嗎?"它看起來于我們之前詢問的數(shù)據(jù)庫問題類似铆隘,但實際上很不相同卓舵。對于數(shù)據(jù)庫,因為應(yīng)用程序和數(shù)據(jù)庫在同一個進(jìn)程中膀钠,它們可以共享相同的全局跟蹤器掏湾,因此更容易解決。對于微服務(wù)肿嘲,這種方式將不兼容融击。因為 OpenTracing 只標(biāo)準(zhǔn)化了跟蹤接口,它沒有標(biāo)準(zhǔn)化跟蹤上下文雳窟。萬維網(wǎng)聯(lián)盟 (W3C) 正在制定跟蹤上下文 (trace context)1? 的標(biāo)準(zhǔn)尊浪,并于 2019-08-09 年發(fā)布了候選推薦標(biāo)準(zhǔn)。OpenTracing 沒有規(guī)定跟蹤上下文的格式封救,而是把決定權(quán)留給了實現(xiàn)它的跟蹤庫际长。結(jié)果每個庫都選擇了自己獨有的的格式。例如兴泥,Zipkin 使用 “X-B3-TraceId” 作為跟蹤 ID工育,Jaeger使用 “uber-trace-id”,因此使用 OpenTracing 并不意味著不同的跟蹤庫可以進(jìn)行跨網(wǎng)互操作搓彻。對于 “Jaeger” 來說有一個好處是你可以選擇使用 “Zipkin 兼容性功能" 13*(https://github.com/jaegertracing/jaeger-client-go/tree/master/zipkin)來生成 Zipkin 跟蹤上下文如绸, 這樣就可以與 Zipkin 相互兼容了。對于其他情況旭贬,你需要自己進(jìn)行手動格式轉(zhuǎn)換(在 “inject” 和 “extract” 之間)怔接。

全鏈路跟蹤設(shè)計

盡量少寫代碼

一個好的全鏈路跟蹤系統(tǒng)不需要用戶編寫很多跟蹤代碼。最理想的情況是你不需要任何代碼稀轨,讓框架或庫負(fù)責(zé)處理它扼脐,當(dāng)然這比較困難。全鏈路跟蹤分成三個跟蹤級別:

  • 跨進(jìn)程跟蹤 ( cross-process )(調(diào)用另一個微服務(wù))

  • 數(shù)據(jù)庫跟蹤

  • 進(jìn)程內(nèi)部的跟蹤 ( in-process )(在一個函數(shù)內(nèi)部的跟蹤)

跨進(jìn)程跟蹤是最簡單的。你可以編寫攔截器或過濾器來跟蹤每個請求瓦侮,它只需要編寫極少的編碼艰赞。數(shù)據(jù)庫跟蹤也比較簡單。如果使用我們上面討論過的封裝器(Wrapper)肚吏,你只需要注冊 SQL 驅(qū)動程序封裝器 (Wrapper) 并將 go-context (里面有跟蹤上下文) 傳入數(shù)據(jù)庫函數(shù)方妖。你可以使用依賴注入 (Dependency Injection)這樣就可以用比較少的代碼來完成此操作。

進(jìn)程內(nèi)跟蹤是最困難的罚攀,因為你必須為每個單獨的函數(shù)編寫跟蹤代碼〉趁伲現(xiàn)在還沒有一個很好的方法,可以編寫一個通用的函數(shù)來跟蹤應(yīng)用程序中的每個函數(shù)(攔截器不是一個好選擇斋泄,因為它需要每個函數(shù)的參數(shù)和返回都必須是一個泛型類型 (interface {}))杯瞻。幸運的是,對于大多數(shù)人來說炫掐,前兩個級別的跟蹤應(yīng)該已經(jīng)足夠了魁莉。

有些人可能會使用服務(wù)網(wǎng)格 (service mesh) 來實現(xiàn)分布式跟蹤,例如 Istio 或Linkerd 卒废。它確實是一個好主意沛厨,跟蹤最好由基礎(chǔ)架構(gòu)實現(xiàn),而不是將業(yè)務(wù)邏輯代碼與跟蹤代碼混在一起摔认,不過你將遇到我們剛才談到的同樣問題逆皮。服務(wù)網(wǎng)格只負(fù)責(zé)跨進(jìn)程跟蹤,函數(shù)內(nèi)部或數(shù)據(jù)庫跟蹤任然需要你來編寫代碼参袱。不過一些服務(wù)網(wǎng)格可以通過提供與流行跟蹤庫的集成电谣,來簡化不同跟蹤庫跨網(wǎng)跟蹤時的的上下文格式轉(zhuǎn)換。

跟蹤設(shè)計:

精心設(shè)計的跨度( span )抹蚀,服務(wù)名稱( service name )剿牺,標(biāo)簽( tag )能充分發(fā)揮全鏈路跟蹤的作用,并使之簡單易用环壤。有關(guān)信息請閱讀語義約定( Semantic Conventions)1?晒来。

將 Trace ID 記錄到日志

將跟蹤與日志記錄集成是一個常見的需求,最重要的是將跟蹤 ID 記錄到整個調(diào)用鏈的日志消息中郑现。目前 OpenTracing 不提供訪問 traceID 的方法湃崩。你可以將 “OpenTracing.SpanContext” 轉(zhuǎn)換為特定跟蹤庫的 “SpanContext”(Zipkin 和Jaeger 都可以通過 “SpanContext” 訪問 traceID)或?qū)?“OpenTracing.SpanContext” 轉(zhuǎn)換為字符串并解析它以獲取 traceID。轉(zhuǎn)換為字符串更好接箫,因為它不會破壞程序的依賴關(guān)系攒读。幸運的是不久的將來你就不需要它了,因為 OpenTracing 將提供訪問 traceID 的方法辛友,請閱讀這里(https://github.com/opentracing/specification/blob/master/rfc/trace_identifiers.md)薄扁。

OpenTracing 和 OpenCensus

OpenCensus1? 不是另一個通用跟蹤接口,它是一組庫,可以用來與其他跟蹤庫集成以完成跟蹤功能邓梅,因此它經(jīng)常與 OpenTracing 進(jìn)行比較脱盲。那么它與 OpenTracing 兼容嗎?答案是否定的震放。因此宾毒,在選擇跟蹤接口時(不論是 OpenTracing 還是 OpenCensus )需要小心驼修,以確保你需要調(diào)用的其他庫支持它殿遂。一個好消息是,你不需要在將來做出選擇乙各,因為它們會將項目合并為一個1?墨礁。

結(jié)論:

全鏈路跟蹤包括不同的場景,例如在函數(shù)內(nèi)部跟蹤耳峦,數(shù)據(jù)庫跟蹤和跨進(jìn)程跟蹤恩静。每個場景都有不同的問題和解決方案。如果你想設(shè)計更好的跟蹤解決方案或為你的應(yīng)用選擇最適合的跟蹤工具或庫蹲坷,那你需要對每種情況都有清晰的了解驶乾。

源碼:

完整源碼的 github 鏈接:https://github.com/jfeng45/grpcservice

索引:

[1]Correlation IDs for microservices architectures
https://hilton.org.uk/blog/microservices-correlation-id

[2]Zipkin
https://zipkin.io

[3]Jaeger: open source, end-to-end distributed tracing
https://www.jaegertracing.io

[4]OpenTracing
https://opentracing.io/docs/getting-started

[5]Zipkin Architecture
https://zipkin.io/pages/architecture.html

[6]The OpenTracing Semantic Specification
https://opentracing.io/specification/

[7]instrumentedsql
https://github.com/ExpansiveWorlds/instrumentedsql

[8]luna-duclos/instrumentedsql
https://github.com/luna-duclos/instrumentedsql

[9]ocsql/driver.go
https://github.com/opencensus-integrations/ocsql/blob/master/driver.go

[10]Trace Context
https://www.w3.org/TR/trace-context/

[11]otgrpc
http://github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc

[12]Go Concurrency Patterns: Context
https://blog.golang.org/context

[13]Zipkin compatibility features
https://github.com/jaegertracing/jaeger-client-go/tree/master/zipkin

[14]Semantic Conventions
https://github.com/opentracing/specification/blob/master/semantic_conventions.md

[15]OpenCensus
https://opencensus.io/

[16]merge the project into>https://medium.com/opentracing/merging-opentracing-and-opencensus-f0fe9c7ca6f0

作者:倚天碼農(nóng)
原文出處:http://1t.click/bqxV

Golang 課程火熱招生資料找WeChat:17812796384

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市循签,隨后出現(xiàn)的幾起案子级乐,更是在濱河造成了極大的恐慌,老刑警劉巖县匠,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件风科,死亡現(xiàn)場離奇詭異,居然都是意外死亡乞旦,警方通過查閱死者的電腦和手機贼穆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兰粉,“玉大人故痊,你說我怎么就攤上這事【凉茫” “怎么了愕秫?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長客峭。 經(jīng)常有香客問我豫领,道長,這世上最難降的妖魔是什么舔琅? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任等恐,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘课蔬。我一直安慰自己囱稽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布二跋。 她就那樣靜靜地躺著战惊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扎即。 梳的紋絲不亂的頭發(fā)上吞获,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音谚鄙,去河邊找鬼各拷。 笑死,一個胖子當(dāng)著我的面吹牛闷营,可吹牛的內(nèi)容都是我干的烤黍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼傻盟,長吁一口氣:“原來是場噩夢啊……” “哼速蕊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娘赴,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤规哲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后筝闹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體媳叨,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年关顷,在試婚紗的時候發(fā)現(xiàn)自己被綠了糊秆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡议双,死狀恐怖痘番,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情平痰,我是刑警寧澤汞舱,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站宗雇,受9級特大地震影響昂芜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赔蒲,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一泌神、第九天 我趴在偏房一處隱蔽的房頂上張望良漱。 院中可真熱鬧,春花似錦欢际、人聲如沸母市。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽患久。三九已至,卻和暖如春浑槽,著一層夾襖步出監(jiān)牢的瞬間蒋失,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工括荡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留高镐,地道東北人溉旋。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓畸冲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親观腊。 傳聞我的和親對象是個殘疾皇子邑闲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容