go-kit 微服务 服务链路追踪(jaeger 实现)(1)

  • 对grpc调用添加链路追踪

部署 jaeger

  • 生产环境部署

  • 本地测试

    • 可以直接用 Jaeger 的 all-in-one

        ```base
      

      sudo docker pull jaegertracing/all-in-one sudo docker run -d -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 -p16686:16686 -p14268:14268 -p9411:9411 jaegertracing/all-in-one:latest ```

    • 能正常访问 http://127.0.0.1:16686/ 则安装成功

编写jaeger 关于grpc代码

初始化客户端

func NewJaegerTracer(serviceName string) (tracer opentracing.Tracer, closer io.Closer, err error) {
	cfg := &jaegerConfig.Configuration{
		Sampler: &jaegerConfig.SamplerConfig{
			Type:  "const", //固定采样
			Param: 1,       //1=全采样、0=不采样
		},
		Reporter: &jaegerConfig.ReporterConfig{
			LogSpans:           true,
			LocalAgentHostPort: "127.0.0.1:6831",
		},
		ServiceName: serviceName,
	}
	tracer, closer, err = cfg.NewTracer(jaegerConfig.Logger(jaeger.StdLogger))
	if err != nil {
		return
	}
	opentracing.SetGlobalTracer(tracer)
	return
}

grpc 客户端中间件

func JaegerClientMiddleware(tracer opentracing.Tracer) grpc.UnaryClientInterceptor {
	return func(
		ctx context.Context,
		method string,
		req, resp interface{},
		cc *grpc.ClientConn,
		invoker grpc.UnaryInvoker,
		opts ...grpc.CallOption,
	) error {
		var parentCtx opentracing.SpanContext
		//先判断ctx里面有没有 span 信息
		//没有就生成一个
		if parent := opentracing.SpanFromContext(ctx); parent != nil {
			parentCtx = parent.Context()
		}
		cliSpan := tracer.StartSpan(
			method,
			opentracing.ChildOf(parentCtx),//父子关系的span关系
			TracingComponentTag,//grcp tag
			ext.SpanKindRPCClient,//客户端 tag
 		)
		defer cliSpan.Finish()
		//从context中获取metadata。md.(type) == map[string][]string
		md, ok := metadata.FromOutgoingContext(ctx)
		if !ok {
			md = metadata.New(nil)
		} else {
			////如果对metadata进行修改,那么需要用拷贝的副本进行修改。
			md = md.Copy()
		}
		//定义一个carrier,下面的Inject注入数据需要用到。carrier.(type) == map[string]string
		//carrier := opentracing.TextMapCarrier{}
		mdWriter := MDReaderWriter{md}
		////将span的context信息注入到carrier中
		err := tracer.Inject(cliSpan.Context(), opentracing.TextMap, mdWriter)
		if err != nil {
			grpclog.Errorf("inject to metadata err %v", err)
		}
		////创建一个新的context,把metadata附带上
		ctx = metadata.NewOutgoingContext(ctx, md)
		err = invoker(ctx, method, req, resp, cc, opts...)
		if err != nil {
			cliSpan.LogFields(log.String("err", err.Error()))
		}
		return err
	}
}

grpc 服务端中间件

func JaegerServerMiddleware(tracer opentracing.Tracer) grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (resp interface{}, err error) {
		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			md = metadata.New(nil)
		}
		spanContext, err := tracer.Extract(opentracing.TextMap, MDReaderWriter{md})
		if err != nil && err != opentracing.ErrSpanContextNotFound {
			grpclog.Errorf("extract from metadata err %v", err)
		}
		serverSpan := tracer.StartSpan(
			info.FullMethod,
			ext.RPCServerOption(spanContext),
			TracingComponentTag,
			ext.SpanKindRPCServer,
		)
		defer serverSpan.Finish()
		ctx = opentracing.ContextWithSpan(ctx, serverSpan)
		return handler(ctx, req)
	}
}

修改服务端方法

......
tracer, _, err := utils.NewJaegerTracer("user_agent_server")
if err != nil {
	utils.GetLogger().Warn("[user_agent] NewJaegerTracer", zap.Error(err))
	quitChan <- err
}
Registar.Register()
utils.GetLogger().Info("[user_agent] grpc run " + *grpcAddr)
chainUnaryServer := grpcmiddleware.ChainUnaryServer(
	grpctransport.Interceptor,
	tils.JaegerServerMiddleware(tracer),
)
baseServer := grpc.NewServer(grpc.UnaryInterceptor(chainUnaryServer))
pb.RegisterUserServer(baseServer, grpcServer)
quitChan <- baseServer.Serve(grpcListener)
......

修改客户端代码

......
tracer, _, err := utils.NewJaegerTracer("user_agent_client")
if err != nil {
	return nil, err
}
......


......
conn, err := grpc.Dial(instance, grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(utils.JaegerClientMiddleware(u.tracer)), )
if err != nil {
	return nil, nil, err
}
srv := u.NewGRPCClient(conn)
......

运行

  • 运行 TestNewUserAgentClient 方法
  • 我们登录 http://127.0.0.1:16686/ jaeger后台查询信息

结语

  • 通过后台界面我们可以看到请求信息,以便与我们对服务的了解
  • jaeger的用法还有很多,这里只展示简单的使用,更加高级的功能欢迎大家一起讨论
  • 欢迎添加QQ一起讨论

完整代码地址

参考文献

联系 QQ: 3355168235