【go-micro實踐】jaeger分佈式鏈路追蹤

github完整代碼地址 我的博客html

安裝jaeger

jaeger提供一個all in one 的docker鏡像,能夠快速搭建實驗環境git

docker run -d --name jaeger 
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 
-p 5775:5775/udp 
-p 6831:6831/udp 
-p 6832:6832/udp 
-p 5778:5778 
-p 16686:16686 
-p 14268:14268 
-p 9411:9411 
jaegertracing/all-in-one:1.6
複製代碼

OpenTracing

OpenTracing經過提供平臺無關、廠商無關的API,使得開發人員可以方便的添加(或更換)追蹤系統的實現。 OpenTracing提供了用於運營支撐系統的和針對特定平臺的輔助程序庫。 jaeger兼容OpenTracing API,因此咱們使用OpenTracing的程序庫能夠方便的替換追蹤工具。 OpenTracing中文文檔github

jaeger使用

封住一下jaeger的初始化操做方便使用,詳細用法能夠查看 jaeger-client-gogolang

// lib/tracer

// NewTracer 建立一個jaeger Tracer
func NewTracer(servicename string, addr string) (opentracing.Tracer, io.Closer, error) {
	cfg := jaegercfg.Configuration{
		ServiceName: servicename,
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:            true,
			BufferFlushInterval: 1 * time.Second,
		},
	}

	sender, err := jaeger.NewUDPTransport(addr, 0)
	if err != nil {
		return nil, nil, err
	}

	reporter := jaeger.NewRemoteReporter(sender)
	// Initialize tracer with a logger and a metrics factory
	tracer, closer, err := cfg.NewTracer(
		jaegercfg.Reporter(reporter),
	)

	return tracer, closer, err
}

func main() {
t, io, err := tracer.NewTracer("tracer", "")
	if err != nil {
		log.Fatal(err)
	}
	defer io.Close()
	opentracing.SetGlobalTracer(t)
}
複製代碼

opentracing.SetGlobalTracer(t) 方法執行會將jaeger tracer註冊到全局,接下來只須要使用opentracing 的標準API即可以了。 若是不想使用jaeger了,想替換成其餘分佈式追蹤工具,只須要工具支持opentracing標準,並將main函數的SetGlobalTracer操做替換便可,其餘文件都不須要更改。docker

micro鏈路追蹤插件

micro自帶的opentracing插件

在micro自帶的插件中已經有opentracing的插件了,包含server,client等,不過這個插件只能go-micro構建的微服務(api,srv)中使用。由於micro網關有一個獨立的插件系統,可是並無提供opentracing相關的插件。api

micro/go-plugins/wrapper/trace/opentracing/opentracing.gobash

咱們能夠在構建服務的時候直接使用,只須要在服務初始化時增長一行函數就能夠了。app

service := micro.NewService(
		micro.Name(name),
		micro.Version("latest"),
		micro.WrapHandler(ocplugin.NewHandlerWrapper(opentracing.GlobalTracer())),
	)
複製代碼

srv/user/main.go 目錄下的user 服務是一個完整的使用實例。分佈式

爲micro網關增長opentracing插件

實現原理

在這裏插入圖片描述
外部HTTP請求首先通過API網關,網關生成第一個SpanContexts而且經過HTTP頭傳遞到聚合層的API服務,這邊須要咱們實現一個插件去作這件事,原理很簡單,攔截每一次請求添加信息就能夠了。 查看micro自帶的opentracing插件,能夠發現是經過golang的context傳遞,micro的RPC已經封裝好了經過context在跨進程服務間傳遞SpanContexts機制,因此咱們須要在API服務層實現一個插件,從HTTP頭中取出SpanContexts並按照micro自帶的方式注入golang context。

// micro opentracing插件中wHandlerWrappe
// NewHandlerWrapper accepts an opentracing Tracer and returns a Handler Wrapper
func NewHandlerWrapper(ot opentracing.Tracer) server.HandlerWrapper {
	return func(h server.HandlerFunc) server.HandlerFunc {
		return func(ctx context.Context, req server.Request, rsp interface{}) error {
			name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
			ctx, span, err := traceIntoContext(ctx, ot, name)
			if err != nil {
				return err
			}
			defer span.Finish()
			return h(ctx, req, rsp)
		}
	}
}
複製代碼

micro API網關插件

lib/wrapper/tracer/opentracing/stdhttp/stdhttp.go函數

和實現JWT鑑權插件同樣,實現一個HTTP中間件經過mciro的插件機制全局註冊就能夠實現攔截每次請求並處理。

// TracerWrapper tracer wrapper
func TracerWrapper(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
		sp := opentracing.GlobalTracer().StartSpan(r.URL.Path, opentracing.ChildOf(spanCtx))
		defer sp.Finish()

		if err := opentracing.GlobalTracer().Inject(
			sp.Context(),
			opentracing.HTTPHeaders,
			opentracing.HTTPHeadersCarrier(r.Header)); err != nil {
			log.Println(err)
		}

		sct := &status_code.StatusCodeTracker{ResponseWriter: w, Status: http.StatusOK}
		h.ServeHTTP(sct.WrappedResponseWriter(), r)

		ext.HTTPMethod.Set(sp, r.Method)
		ext.HTTPUrl.Set(sp, r.URL.EscapedPath())
		ext.HTTPStatusCode.Set(sp, uint16(sct.Status))
		if sct.Status >= http.StatusInternalServerError {
			ext.Error.Set(sp, true)
		}
	})
}
複製代碼

Tracer相關的概念能夠查看這個文檔

  1. opentracing.GlobalTracer().Extract 方法提取HTTP頭中的spanContexts
  2. opentracing.ChildOf 方法基於提取出來的spanContexts生成新的child spanContexts
  3. opentracing.GlobalTracer().StartSpan 方法生成一個新的span
  4. github.com/opentracing/opentracing-go/ext 經過ext能夠爲追蹤添加一些tag來展現更多信息,好比URL,請求類型(GET,POST...), 返回碼
  5. sp.Finish() 結束這一個span

API服務(使用gin)插件

lib/wrapper/tracer/opentracing/gin2micro/gin2micro.go

// TracerWrapper tracer 中間件
func TracerWrapper(c *gin.Context) {
	md := make(map[string]string)
	spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
	sp := opentracing.GlobalTracer().StartSpan(c.Request.URL.Path, opentracing.ChildOf(spanCtx))
	defer sp.Finish()

	if err := opentracing.GlobalTracer().Inject(sp.Context(),
		opentracing.TextMap,
		opentracing.TextMapCarrier(md)); err != nil {
		log.Log(err)
	}

	ctx := context.TODO()
	ctx = opentracing.ContextWithSpan(ctx, sp)
	ctx = metadata.NewContext(ctx, md)
	c.Set(contextTracerKey, ctx)

	c.Next()

	statusCode := c.Writer.Status()
	ext.HTTPStatusCode.Set(sp, uint16(statusCode))
	ext.HTTPMethod.Set(sp, c.Request.Method)
	ext.HTTPUrl.Set(sp, c.Request.URL.EscapedPath())
	if statusCode >= http.StatusInternalServerError {
		ext.Error.Set(sp, true)
	}
}

// ContextWithSpan 返回context
func ContextWithSpan(c *gin.Context) (ctx context.Context, ok bool) {
	v, exist := c.Get(contextTracerKey)
	if exist == false {
		ok = false
		return
	}

	ctx, ok = v.(context.Context)
	return
}
複製代碼

基本操做流程和給micro編寫的插件相同,可是有兩點不一樣。其一,由於我使用gin開發API服務,因此基於gin的API。其二,由於micro內部提供經過golang context傳遞spanContexts的機制,因此將這邊會將child spanContexts注入到gin 的context,在API服務經過micro提供RPC接口(生成的XX.micro.go文件中調用函數第一個參數都是context)調用其餘服務時傳入提取的context,以下:

...
ctx, ok := gin2micro.ContextWithSpan(c)
	if ok == false {
		log.Log("get context err")
	}

	res, err := s.helloC.Call(ctx, &helloS.Request{Name: "xuxu"})
...
複製代碼

完整的實現細節能夠查看,github倉庫中 lib/wrapper/tracer/opentracing, 這裏

完總體驗

在這裏插入圖片描述

在這裏插入圖片描述
項目完整 代碼地址
相關文章
相關標籤/搜索