golang+jaeger實現鏈路追蹤

本文將講解jaeger基本概念,基於golang的代碼實現以及注入原理

jaeger 概述

組件概念:

  • jaeger-client
  • jaeger-agent 將client發送的span發送到collector
  • jaeger-collector 收集數據並存儲或發送到隊列
  • jaeger ingester 讀取kafka隊列寫入存儲
  • jaeger-query 查詢數據展示tracer

邏輯概念:

  • span 具體的某個操作,包含以下屬性 操作名稱 開始時間 執行時長 logs # 捕獲指定時間的消息,或調試輸出 tags # 不被繼承,查詢過濾理解追蹤數據
  • Trace 是一個完整的執行過程,是span的有向無環圖
  • SpanContext # 傳遞給下級span的信息trace_id,span_id,parentId等
  • Baggage 存儲在SpanContext的鍵值集合,在一個鏈路上全局傳輸

應用代碼實現

單span代碼

 // 從環境變量獲取配置參數
cfg,err:=jaegercfg.FromEnv()
if err!=nil {
log.Println(err)
}
cfg.Sampler=&jaegercfg.SamplerConfig{
Type: "const",// 使用const採樣器
Param: 1, // 採樣所有追蹤
}
// 設置服務名
cfg.ServiceName = "jaeger tracer demo"
// 根據創建Tracer
tracer, closer, err := cfg.NewTracer()
if err != nil {
log.Println(err)
}
defer closer.Close()
//設置全局tracer
opentracing.SetGlobalTracer(tracer)
//創建一個span
parentSpan:=tracer.StartSpan("root")
defer parentSpan.Finish()
parentSpan.LogFields(
tracelog.String("hello","world"),
)
parentSpan.LogKV("foo","bar")
// 創建一個childspan
childspan:=tracer.StartSpan("child span",opentracing.ChildOf(parentSpan.Context()))
defer childspan.Finish()

誇進程傳播

使用上下文傳遞

ctx := opentracing.ContextWithSpan(context.Background(), span)
span, _ := opentracing.StartSpanFromContext(ctx, "req svc2")

跨請求傳播

將span注入header

 ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, url)
ext.HTTPMethod.Set(span, "GET")
span.Tracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header),
)

從header獲取span上下文,並根據上下文創建新span

 spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("get haha", ext.RPCServerOption(spanCtx))

跨應用多span示例代碼

有兩個服務組成,svc1,svc2
有三個span組成,svc1 sayhello → svc1 req svc2 → svc2 get bagger
svc1代碼

package main
import (
"context"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/uber/jaeger-client-go/config"
"log"
"net/http"
)
func main() {
cfg, err := config.FromEnv()
if err != nil {
log.Println(err)
}
cfg.ServiceName = "svc1"
cfg.Sampler = &config.SamplerConfig{
Type: "const",

Param: 1,
}
tracer, closer, err := cfg.NewTracer()
if err != nil {
log.Println(err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
span := tracer.StartSpan("say hello")
span.SetTag("role", "root")
span.LogKV("hello", "world")
defer span.Finish()
ctx := opentracing.ContextWithSpan(context.Background(), span)
testchildspan(ctx)
}
func testchildspan(ctx context.Context) {
url := "http://localhost:8000"
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println(err)
}
span, _ := opentracing.StartSpanFromContext(ctx, "req svc2")
defer span.Finish()
span.SetTag("role", "childspan")
span.SetBaggageItem("haha", "heihei")
ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, url)
ext.HTTPMethod.Set(span, "GET")
span.Tracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header),
)
resp, err := client.Do(req)
if err != nil {
log.Println(err)
}
log.Println(resp.Status)
}

svc2代碼

package main
import (
"net/http"
"log"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"

otlog "github.com/opentracing/opentracing-go/log"
"github.com/uber/jaeger-client-go/config"
)
func main(){
http.HandleFunc("/",test)
http.ListenAndServe(":8000",nil)
}
func test(w http.ResponseWriter,r *http.Request){
log.Println(r.Header,r.URL)
cfg,err:=config.FromEnv()
if err!=nil {
log.Println(err)
}
cfg.ServiceName="svc2"
cfg.Sampler=&config.SamplerConfig{
Type: "const",
Param: 1,
}
tracer,closer,err:=cfg.NewTracer()
if err!=nil {
log.Println(err)
}
defer closer.Close()
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("get haha", ext.RPCServerOption(spanCtx))
defer span.Finish()
log.Println(span.BaggageItem("haha"))
span.LogFields(
otlog.String("event", "string-format"),
otlog.String("value", "hello wrold"),
)
w.Write([]byte("hello wrold"))
}

header數據解析

對於上述代碼我們發現svc1發送給svc2有如下特殊header
Uber-Trace-Id:[518ee099f68f3974:17531754249a513a:518ee099f68f3974:1]
Uberctx-Haha:[heihei]
那這些header是如何根據tracer信息添加呢

根據傳入的format獲取對應的injector

Inject代碼實現

func (t *Tracer) Inject(ctx opentracing.SpanContext, format interface{}, carrier interface{}) error {
c, ok := ctx.(SpanContext)
if !ok {
return opentracing.ErrInvalidSpanContext
}
if injector, ok := t.injectors[format]; ok {
return injector.Inject(c, carrier)
}
return opentracing.ErrUnsupportedFormat
}

我們看到是根據format拿到Injector,默認支持三種類型

  • binary
  • TextMap
  • HTTPHeaders
    其中HTTPHeaders基於TextMap傳播Propagator 兩種Propagator實現了Injector接口
  • BinaryPropagator
  • TextMapPropagator
    BinaryPropagator的不再講述

TextMapPropagator

func (p *TextMapPropagator) Inject(
sc SpanContext,
abstractCarrier interface{},
) error {
textMapWriter, ok := abstractCarrier.(opentracing.TextMapWriter)
if !ok {
return opentracing.ErrInvalidCarrier
}
// 不要使用trace context對字符串進行編碼

// 設置trace 上下文header
// 默認值TraceContextHeaderName = "uber-trace-id"
textMapWriter.Set(p.headerKeys.TraceContextHeaderName, sc.String())
// 設置baggage
for k, v := range sc.baggage {
safeKey := p.addBaggageKeyPrefix(k)
safeVal := p.encodeValue(v)
textMapWriter.Set(safeKey, safeVal)
}
return nil
}

carrier

Propagator通過carrier注入提取數據
binary直接傳入實現io.Writer接口對象即可
TextMapPropagator有兩種carrier

  • HTTPHeadersCarrier
  • TextMapCarrier

HTTPHeadersCarrier

type HTTPHeadersCarrier http.Header
// 符合TextMapWriter接口
func (c HTTPHeadersCarrier) Set(key, val string) {
h := http.Header(c)
h.Set(key, val)
}
// 符合TextMapReader接口
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
for k, vals := range c {
for _, v := range vals {
if err := handler(k, v); err != nil {
return err
}
}
}

return nil
}

TextMapCarrier

type TextMapCarrier map[string]string
// ForeachKey conforms to the TextMapReader interface.
func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error {
for k, v := range c {
if err := handler(k, v); err != nil {
return err
}
}
return nil
}
// Set implements Set() of opentracing.TextMapWriter
func (c TextMapCarrier) Set(key, val string) {
c[key] = val
}

結果

TraceContextHeaderName 默認值為 “uber-trace-id”
設置該值代碼為:

func (c SpanContext) String() string {
if c.traceID.High == 0 {
return fmt.Sprintf("%x:%x:%x:%x", c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.samplingState.stateFlags.Load())
}
return fmt.Sprintf("%x%016x:%x:%x:%x", c.traceID.High, c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.samplingState.stateFlags.Load())
}

由此看出該header包含了traceID,spanID,parentID


分享到:


相關文章: