opentracing-go原始碼閱讀一

cdh0805010118發表於2018-07-02

Tracer

Tracer Interface

type Tracer interface{
    StartSpan(operationName string, opts ...StartSpanOption) Span

    Inject(sm SpanContext, format interface{}, carrier interface{}) error

    Extract(format interface{}, carrier interface{}) (SpanContext, error)
}

以上是實現一個tracer呼叫鏈跟蹤的最小子集,任何一個底層跟蹤系統的實現,如果實現了它,則可以把整個呼叫鏈串起來。

在Tracer interface中StartSpan方法傳參,傳入Span結構體引數,如:SpanReference列表建立時間Span Tag資訊;其中:

SpanReference列表:表示新建立的Span與【curr_depth+1】Span之間的關係,如FollowsFrom和ChildOf;注意,這個是RPC/IPC跨程式呼叫關係,所以SpanContext表示OpenTracing標準中可以攜帶Baggage資訊;

Tags: 型別:map[string]interface{}, 生命週期:span執行單元;

因為SpanReference, StartTimeTags三個變數為StartSpanOptions引數,所以必須實現StartSpanOption介面中Apply方法。 這個StartSpanOptions代表Span的必要引數子集

globaltracer

每一個微服務都會有一個globaltracer值, 我們通過SetGlobalTracer方法設定業務系統採用的分散式跟蹤系統產品;一個產品會有1個到多個微服務,則每個微服務都需要指定全域性Tracer變數值,這一個Tracer就代表一個遵循OpenTracing標準的廠商產品,在一個產品中,所有微服務的Tracer變數值都必須指向同一個廠商產品,否則,分散式跟蹤系統服務生效

其中,我們可以通過GlobalTracer()方法獲取微服務中的Tracer值;

如果我們沒有對Tracer進行初始化,則意味著沒有指定採用哪個廠商的分散式跟蹤系統產品,則預設情況下,採用opentracing-go底層標準下的預設空Tracer——NoopTracer,這個是在實現OpenTracing標準API庫時,必須要做的,因為如果業務系統採用的元件或者第三方庫中有探針,如果不實現空的Tracer,則直接報錯,無法使用,從而導致Tracer與業務強耦合了。

NoopTracer

微服務中預設的空Tracer,包含:NoopTracer、noopSpan和noopSpanContext三個變數值;其中:

  1. noopSpanContext是在SpanReference中使用,它代表SpanContext上下文傳輸tracer時的span獲取。並攜帶Baggage資訊;ForeachBaggageItem(func(k, v string) bool){}
  2. noopSpan實現了Span interface。方法實現全部為空;
  3. noopTracer實現了Tracer interface,包括StartSpan、Inject和Extract三個方法;全部為空實現;

微服務中,NoopTracer是Tracer未指定時的預設實現,不會在Tracer中生成任何資料,也不會產生Tracer呼叫鏈路;

Span

Span interface

type Span interface{
    // Span執行單元結束
    Finish()
    // 帶結束時間和日誌記錄列表資訊,Span執行單元結束;也就是說結束時間可以業務指定; 日誌列表也可以直接新增; 目前還不知道這個的使用場景;
    FinishWithOptions(opts FinishOptions)
    // 把Span的Baggage封裝成SpanContext
    Context() SpanContext
    // 設定Span的操作名稱
    SetOperationName(operationName string) Span
    // 設定Span Tag
    SetTag(key string, value interface{}) Span
    // 設定Span的log.Field列表;
    // span.LogFields(
    //      log.String("event", "soft error"),
    // )
    LogFields(fields ...log.Field) 
    // key-value列表={"event": "soft error", "type": "cache timeout", "waited.millis":1500}
    LogKV(alternatingKeyValues ...interface{})
    // LogFields與LogKV類似,只是前者已封裝好;

    // 設定span的Baggage:key-value, 用於跨程式上下文傳輸
    SetBaggageItem(restrictedKey, value string) Span
    // 通過key獲取value;
    BaggageItem(restrictedKey string) string
    // 獲取Span所在的呼叫鏈tracer
    Tracer() Tracer
    // 廢棄,改用LogFields 或者 LogKV
    LogEvent(event string)
    // 同上
    LogEventWithPayload(event string, payload interface{});
    // 同上
    Log(data LogData)
}

SpanContext

首先,需要看一小段程式碼:

package main

import "fmt"

type Fun struct{}

func main() {
    var fun1, fun2 = Fun{}, Fun{}
    if fun1 == fun2 {
        fmt.Println("fun1==fun2")
    } else {
        fmt.Println("fun1!=fun2")
    }
}

// 執行結果:fun1==fun2
// 比較兩個值是否相等,取決於:值和型別,都相等這表示相同;

Context用於上下文資料傳輸使用,在OpenTracing標準中,Span之間跨程式呼叫時,會使用SpanContext傳輸Baggage攜帶資訊。通過context標準庫實現;如:

type contextKey struct{}

var activeSpanKey = contextKey{}

// 封裝span到context中
func ContextWithSpan(ctx context.Context, span Span) context{
    return context.WithValue(ctx, activeSpanKey, span)
}

// 從ctx中通過activeSpanKey取出Span,這裡可以看到不同服務的activeSpanKey值,是相同的,上面的DEMO可以說明。
func SpanFromContext(ctx context.Context) Span{
    val:=ctx.Value(activeSpanKey)
    if sp, ok := val.(Span); ok{
        return sp
    }
    return nil
}

通過context上下文的activeSpanKey,我們可以獲得Span,並建立新的span,如:

func startSpanFromContextWithTracer(ctx context.Context, tracer Tracer, operationName string, opts ...StartSpanOption) (Span, context.Context){
    // 首先從上下文看是否能夠獲取到span,如果獲取不到,再建立tracer和span;
    if parentSpan:= SpanFromContext(ctx); parentSpan !=nil {
        opts = append(opts, ChildOf(parentSpan.Context()))
    }

    span := tracer.StartSpan(operationName, opts...)
    return span, ContextWithSpan(ctx, span)
}

參考資料

opentracing-go

相關文章