opentracing: jaeger在grpc中的簡單實現

參考項目:https://github.com/grpc-ecosystem/grpc-opentracing

之前用函數調用實現了簡單jaeger-demo(https://blog.csdn.net/liyunlong41/article/details/87932953),函數之間利用context傳遞span信息。現在開始在grpc請求中實現簡單的grpc-jaeger-demo,span的傳遞渠道也是利用context。

但是也稍有不同,我們之前是用StartSpanFromContext來模擬從context中啓動一個子span,但是StartSpanFromContext或者SpanFromContext只能在同一個服務內使用,grpc中client的context和server的context並不是同一個context,無法使用這兩個函數。(參考https://github.com/grpc/grpc-go/issues/130

如果想通過grpc的context傳遞span的信息,就需要使用grcp的metadata來傳遞(一個簡單的例子:https://medium.com/@harlow/grpc-context-for-client-server-metadata-91cec8729424)。

同時grpc-client端提供了Inject函數,可以將span的context信息注入到carrier中,再將carrier寫入到metadata中,即可完成span信息的傳遞。

grpc提供了攔截器,我們可以在dial函數裏設置攔截器,這樣每次請求都會經過攔截器,我們不需要在每個接口中去編寫重複的代碼。

client端示例代碼:


func main() {
	//init jaeger
	tracer, closer, err := initJaeger("client", jaegerAgentHost)
	if err != nil {
		log.Fatal(err)
	}
	defer closer.Close()
	//dial
	conn, err := grpc.Dial(addr, grpc.WithInsecure(), clientDialOption(tracer))
	if err != nil {
		log.Fatalf("dial fail, %+v\n", err)
	}
	//發送請求
	req := &delayqueue.PingRequest{Msg:"ping~"}
	client := delayqueue.NewDelayQueueClient(conn)
	r, err := client.Ping(context.Background(), req)

	fmt.Println(r, err)
}

func clientDialOption(tracer opentracing.Tracer) grpc.DialOption {
	return grpc.WithUnaryInterceptor(jaegerGrpcClientInterceptor)
}
type TextMapWriter struct {
	metadata.MD
}
//重寫TextMapWriter的Set方法,我們需要將carrier中的數據寫入到metadata中,這樣grpc纔會攜帶。
func (t TextMapWriter) Set(key, val string) {
	//key = strings.ToLower(key)
	t.MD[key] = append(t.MD[key], val)
}

func jaegerGrpcClientInterceptor (ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
	var parentContext opentracing.SpanContext
	//先從context中獲取原始的span
	parentSpan := opentracing.SpanFromContext(ctx)
	if parentSpan != nil {
		parentContext = parentSpan.Context()
	}
	tracer := opentracing.GlobalTracer()
	span := tracer.StartSpan(method, opentracing.ChildOf(parentContext))
	defer span.Finish()
	//從context中獲取metadata。md.(type) == map[string][]string
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		md = metadata.New(nil)
	} else {
		//如果對metadata進行修改,那麼需要用拷貝的副本進行修改。(FromIncomingContext的註釋)
		md = md.Copy()
	}
	//定義一個carrier,下面的Inject注入數據需要用到。carrier.(type) == map[string]string
	//carrier := opentracing.TextMapCarrier{}
	carrier := TextMapWriter{md}
	//將span的context信息注入到carrier中
	e := tracer.Inject(span.Context(), opentracing.TextMap, carrier)
	if e != nil {
		fmt.Println("tracer Inject err,", e)
	}
	//創建一個新的context,把metadata附帶上
	ctx = metadata.NewOutgoingContext(ctx, md)

	return invoker(ctx, method, req, reply, cc, opts...)
}

func initJaeger(service string, jaegerAgentHost string) (tracer opentracing.Tracer, closer io.Closer, err error) {
	cfg := &config.Configuration{
		Sampler: &config.SamplerConfig{
			Type:  "const",
			Param: 1,
		},
		Reporter: &config.ReporterConfig{
			LogSpans: true,
			LocalAgentHostPort:jaegerAgentHost,
		},
	}
	tracer, closer, err = cfg.New(service, config.Logger(jaeger.StdLogger))
	opentracing.SetGlobalTracer(tracer)
	return tracer, closer, err
}

在grpc-server端,我們使用Extract函數將carrier從metadata中提取出來,這樣client端與server端就能建立span信息的關聯。我們在server端同樣只是修改攔截器,在grpc.NewServer時將我們的攔截器傳進去。

server端代碼:

func serverOption(tracer opentracing.Tracer) grpc.ServerOption {
	return grpc.UnaryInterceptor(jaegerGrpcServerInterceptor)
}

type TextMapReader struct {
	metadata.MD
}
//讀取metadata中的span信息
func (t TextMapReader) ForeachKey(handler func(key, val string) error) error { //不能是指針
	for key, val := range t.MD {
		for _, v := range val {
			if err := handler(key, v); err != nil {
				return err
			}
		}
	}
	return nil
}

func jaegerGrpcServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	//從context中獲取metadata。md.(type) == map[string][]string
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		md = metadata.New(nil)
	} else {
		//如果對metadata進行修改,那麼需要用拷貝的副本進行修改。(FromIncomingContext的註釋)
		md = md.Copy()
	}
	carrier := TextMapReader{md}
	tracer := opentracing.GlobalTracer()
	spanContext, e := tracer.Extract(opentracing.TextMap, carrier)
	if e != nil {
		fmt.Println("Extract err:", e)
	}

	span := tracer.StartSpan(info.FullMethod, opentracing.ChildOf(spanContext))
	defer span.Finish()
	ctx = opentracing.ContextWithSpan(ctx, span)

	return handler(ctx, req)
}

我們可以在span finish之前利用SetTag添加一些額外的信息,例如request和reply,以及error信息,但是這些信息是不會在client和server中傳遞的,我們可以在UI中每個span中顯示出他們的tag。

WebUI:

下面就是webUI中效果圖了,簡單展示一下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章