go基於grpc構建微服務框架-集成opentracing

1.概述

存在這樣一種場景,當咱們進行微服務拆分後,一個請求將會通過多個服務處理以後再返回,這時,若是在請求的鏈路上某個服務出現故障時,排查故障將會比較困難.
咱們可能須要將請求通過的服務,挨個查看日誌進行分析,當服務有幾十上百個實例時,這無疑是可怕的.所以爲了解決這種問題,調用鏈追蹤應運而生.html

2.opentracing

1.1 opentracing做用

調用鏈追蹤最早由googel在Dapper這篇論文中提出,OpenTracing主要定義了相關的協議以及接口,這樣各個語言只要按照Opentracing的接口以及協議實現數據上報,那麼調用信息就能統一被收集.
java

如上圖所示,接口可能首先通過web框架,而後調用auth服務,經過調用鏈,將請求通過的服務進行編號,統一收集起來,造成邏輯上的鏈路,這樣,咱們就能夠看到請求通過了哪些服務,從而造成服務依賴的拓撲.git

如上,總鏈路由每段鏈路組成,每段鏈路均表明通過的服務,耗時可用於分析系統瓶頸,當某個請求返回較慢時,能夠經過排查某一段鏈路的耗時狀況,從而分析是哪一個服務出現延時較高,今個到具體的服務中分析具體的問題.github

1.2 opentraing關鍵術語

  • Traces(調用鏈)

一次調用的鏈路,由TraceID惟一標誌,如一次請求則一般爲一個trace,trace由全部途徑的span組成.web

  • Spans(調用跨度)

沒進過一個服務則將span,一樣每一個span由spanID惟一標誌.redis

  • Span Tags(跨度標籤)

span的標籤,如一段span是調用redis的,而能夠設置redis的標籤,這樣經過搜索redis關鍵字,咱們就能夠查詢出全部相關的span以及trace.app

  • Baggage Item(附帶數據)

附加的數據,由key:value組成,經過附加數據,能夠給調用鏈更多的描述信息,不過考慮到傳輸問題,附加數據應該儘量少.框架

1.3 jaeger & zipkin

目前開源的實現有zipkin以及jaeger分佈式

  • zipkin

zipkin主要由java編寫,經過各個語言的上報庫實現將數據上報到collector,collector再將數據存儲,並經過API提供給前段UI展現.函數

  • jaeger

jaeger由go實現,由uber開發,目前是cloud native項目,流程與zipkin相似,增長jager-agent這樣個組件,這個組件官方建議是每一個機器都部署一個,經過這個組件再將數據上報到collector存儲展現,另外,裏面作了對zipkin的適配,其實一開始他們用的也是zipkin,爲毛後面要本身造輪子?見他們的解釋. 連接

總的來講二者都能基本知足opentracing的功能,具體的選擇能夠結合自身技術棧和癖好.

2. grpc集成opentracing

grpc集成opentracing並不難,由於grpc服務端以及調用端分別聲明瞭UnaryClientInterceptor以及UnaryServerInterceptor兩個回調函數,所以只須要重寫這兩個回調函數,並在重寫的回調函數中調用opentracing接口進行上報便可.
初始化時傳入重寫後的回調函數,同時二選一初始化jager或者zipkin,而後你就能夠開啓分佈式調用鏈追蹤之旅了.

完整的代碼見grpc-wrapper

2.1 client端

// OpenTracingClientInterceptor  rewrite client's interceptor with open tracing
func OpenTracingClientInterceptor(tracer opentracing.Tracer) grpc.UnaryClientInterceptor {
    return func(
        ctx context.Context,
        method string,
        req, resp interface{},
        cc *grpc.ClientConn,
        invoker grpc.UnaryInvoker,
        opts ...grpc.CallOption,
    ) error {

        //從context中獲取spanContext,若是上層沒有開啓追蹤,則這裏新建一個
        //追蹤,若是上層已經有了,測建立子span.
        var parentCtx opentracing.SpanContext
        if parent := opentracing.SpanFromContext(ctx); parent != nil {
            parentCtx = parent.Context()
        }
        cliSpan := tracer.StartSpan(
            method,
            opentracing.ChildOf(parentCtx),
            wrapper.TracingComponentTag,
            ext.SpanKindRPCClient,
        )
        defer cliSpan.Finish()

        //將以前放入context中的metadata數據取出,若是沒有則新建一個metadata
        md, ok := metadata.FromOutgoingContext(ctx)
        if !ok {
            md = metadata.New(nil)
        } else {
            md = md.Copy()
        }
        mdWriter := MDReaderWriter{md}

        //將追蹤數據注入到metadata中
        err := tracer.Inject(cliSpan.Context(), opentracing.TextMap, mdWriter)
        if err != nil {
            grpclog.Errorf("inject to metadata err %v", err)
        }
        //將metadata數據裝入context中
        ctx = metadata.NewOutgoingContext(ctx, md)
        //使用帶有追蹤數據的context進行grpc調用.
        err = invoker(ctx, method, req, resp, cc, opts...)
        if err != nil {
            cliSpan.LogFields(log.String("err", err.Error()))
        }
        return err
    }
}

2.2 server端

//OpentracingServerInterceptor rewrite server's interceptor with open tracing
func OpentracingServerInterceptor(tracer opentracing.Tracer) grpc.UnaryServerInterceptor {
    return func(
        ctx context.Context,
        req interface{},
        info *grpc.UnaryServerInfo,
        handler grpc.UnaryHandler,
    ) (resp interface{}, err error) {
                //從context中取出metadata
        md, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            md = metadata.New(nil)
        }
               //從metadata中取出最終數據,並建立出span對象
        spanContext, err := tracer.Extract(opentracing.TextMap, MDReaderWriter{md})
        if err != nil && err != opentracing.ErrSpanContextNotFound {
            grpclog.Errorf("extract from metadata err %v", err)
        }
                //初始化server 端的span
        serverSpan := tracer.StartSpan(
            info.FullMethod,
            ext.RPCServerOption(spanContext),
            wrapper.TracingComponentTag,
            ext.SpanKindRPCServer,
        )
        defer serverSpan.Finish()
        ctx = opentracing.ContextWithSpan(ctx, serverSpan)
             //將帶有追蹤的context傳入應用代碼中進行調用
        return handler(ctx, req)
    }
}

因爲opentracing定義了相關的接口,而jaeger以及zipkin進行了相應的實現,所以這裏可使用jaeger的也可使用zipkin進行上報.

3.效果

jaeger服務主頁信息

每條調用鏈信息

4.參考

zipkin
jaeger
OpenTracing
grpc-wrapper

相關文章
相關標籤/搜索