basictracer-go原始碼閱讀——event&propagation

cdh0805010118發表於2018-07-04

event

這個event是有關span操作時產生的事件,這些事件統稱為:SpanEvent,相關事件資料儲存格式如下:

// span建立時產生的事件
type EventCreate struct {OperationName string}

// 儲存tag操作時的事件
type EventTag struct{
    Key string
    Value interface{}
}

// 儲存baggage時的事件
type EventBaggage struct {
    Kye, Value string
}

// 儲存日誌時的事件
type EventLogFields opentracing.LogRecord

// span Finish時的事件
type EventFinish RawSpan

// 針對這些事件資料型別,有一些span操作事件的方法,如下所示:
func (s *spanImpl) onCreate(opName string) {
    if s.event !=nil {
        s.event(EventCreate{OperationName: opName})
    }
}

func (s *spanImpl) onTag(key string, value interface{}){
    if s.event !=nil {
        s.event(EventTag{Key: key, Value: value})
    }
}

func (s *spanImpl) onLogFields(lr opentracing.LogRecord){
    if s.event !=nil {
        s.event(EventLogFields(lr))
    }
}

func (s *spanImpl) onBaggage(key, value string) {
    if s.event !=nil {
        s.event(EventBaggage{Key: key, Value: value})
    }
}

func (s *spanImpl) onFinish(sp RawSpan) {
    if s.event!=nil {
        s.event(EventFinish(sp))
    }
}
// 以上這些都是針對span發生變化時的相關事件,這些可以記錄,也可以不記錄,取決於span.event是否被關注。

propagation

propagation主要是IPC/RPC跨程式資料傳輸。它包括上下文資訊攜帶的資料儲存格式,以及網路傳輸資料與本地資料格式轉換

我們在《basictracer原始碼閱讀——TracerImpl》的SpanImpl部分說過三種Baggage攜帶格式,包括TextMapPropagator、BinaryPropagator和AccessPropagator。

這三種資料型別存在兩種行為:Inject和Extract。用來進行SpanContext和Baggages之間的資料轉換

textMapPropagator

// 父級:Client或者Producer
func (p *textMapPropagator) Inject(spanContext opentracing.SpanContext,
opaqueCarrier interface{}) error{
    sc, ok := spanContext.(SpanContext) // 獲取span本身相關資訊
    // 這個無需指明或者猜測是否為HTTPHeader或者TextMap,因為這是opentracing-go底層透明實現Set和ForeachKey兩個方法
    carrier, ok := opaqueCarrier.(opentracing.TextMapWriter)

    // 把SpanContext需要上下文攜帶的資訊寫入到網路傳輸資料中
    carrier.Set(fieldNameTraceID, strconv.FormatUint(sc.TraceID, 16))
    carrier.Set(fieldNameSpanID, strconv.FormatUint(sc.SpanID, 16))
    carrier.Set(fieldNameSampled, strconv.FormatBool(sc.Sampled))

    // baggage資訊, 在SpanImpl章節說過,為了凸顯TraceID、SpanID和Sampled三個變數的價值,沒有放入到Baggage中。
    for k, v := range sc.Baggage{
        carrier.Set(prefixBaggage+k, v)
    }
    return nil
}

// 子級:Server或者Consumer
func (p *textMapPropagator) Extract(opaqueCarrier interface{}) (opentracing.SpanContext, error) {
    // opaqueCarrier資料格式為HTTPHeader或者TextMap,這不用上層關心,因為這兩者已經實現了ForeachKey方法
    carrier, ok := opaqueCarrier.(opentracing.TextMapReader)

    carrier.ForeachKey(func(k, v string) error{
        ... // 根據Inject方法,解出對應的SpanContext相關資訊
        // 包括TraceID、SpanID、Sampled和Baggage資訊
    })
}

binaryPropagator

它是網路傳輸位元流,通過io.Reader和io.Writer進行流讀寫操作

// 父級:Client或者Producer
func (p *binaryPropagator) Inject(spanContext opentracing.SpanContext,
opaqueCarrier interface{}) error{   
    // 獲取span的SpanContext
    sc, ok := spanContext.(SpanContext)
    // 把SpanContext通過io.Writer, 採用protobuffer協議,
    // 把它轉換為網路流中資料opaqueCarrier
    carrier, ok := opaqueCarrier.(io.Writer)

    // 然後把資料流通過pb協議和大端模式轉化為網路流carrier
    state:=wire.TracerState{}
    state.TraceId = sc.TraceID
    state.SpanId = sc.SpanID
    state.Sampled = sc.Sampled
    state.BaggateItems = sc.Baggage // pb 支援map資料格式
    b, err :=proto.Marshal(&state)

    length:=uint32(len(b))
    binary.Write(carrier, binary.BigEndian, &length)
    carrier.Write(b)
    // 這裡要注意的是,網路資料傳輸的格式是[length(b)+b]。讀取的時候,前uint32八個位元組為資料長度,偏移8位元組後,計算uint32儲存值,則是實際資料大小
    // 在linux記憶體管理基本上都是這種資料傳輸形式,例如: 儲存struct{...}格式的資料流為:struct本身大小+struct中資料大小+struct資料體
}

// 子級:Server或者Consumer
func (p *binaryPropagator) Extract(opaqueCarrier interface{}) (opentracing.SpanContext, error) {
    // 獲取binary網路流
    carrier, ok := opaqueCarrier.(io.Reader)
    binary.Read(carrier, binary.BigEndian, &length) //var length uint32
    buf := make([]byte, length) 
    // 以上我們可以看到,Inject注入到網路流的資料大小,Extract解析時也是一樣的大小解析
    carrier.Read(buf)
    // buf是pb協議儲存, 並通過pb協議解析
    ctx:=wire.TracerState{}
    proto.Unmarshal(buf, &ctx)
    return SpanContext{
        TraceID: ctx.TraceId,
        SpanID: ctx.SpanId,
        Sampled: ctx.Sampled,
        Baggage: ctx.BaggageItems,
    }
}

accessPropagator

basictracer開放出了一個可供廠商自定義SpanContext與網路資料傳輸的儲存協議。

type DelegatingCarrier interface{
    // 因為要遵循basicTracer的SpanContext,所以提供了SetState方法,讀寫traceID、SpanID和Sampled
    SetState(traceID, spanID uint64, sampled bool)
    State() (traceID, spanID uint64, sampled bool)
    // 再就是需要對SpanContext和Carrier進行資料轉換
    // 這兩個方法類似於TextMapReader和TextMapWriter介面
    SetBaggageItem(key, value string)
    GetBaggage(func(key, value string))
}

// 把SpanContext轉換為網路可傳輸的Carrier,依賴於具體實現
func (p *accessorPropagator) Inject(spanContext opentracing.SpanContext,
carrier interface{}) error{
    // 轉換SpanContext到Carrier中
    dc, ok := carrier.(DelegatingCarrier)
    sc, ok := spanContext.(SpanContext)
    dc.SetState...
    for k, v:= range sc.Baggage{
        dc.SetBaggageItem(k, v)
    }
}

// 把網路傳輸資料Carrier轉換為SpanContext
func ( p *accessorPropagator) Extract(carrier interface{}) (opentracing.SpanContext, error) {
    // 網路資料校驗是否為DeletingCarrier
    dc, ok := carrier.(DelegatingCarrier)
    // 獲取Span相關資訊
    traceID, spanID, sampled:= dc.State()
    sc := SpanContext{
        TraceID: traceID,
        SpanID: spanID,
        Sampled: sampled,
        Baggage: nil,
    }
    // 遍歷獲取baggage
    dc.GetBaggage(func(k, v string) {
        ...
        sc.Baggage[k] = v
    })
    return sc, nil
}

相關文章