链路追踪 - jeager

opentracing的官方文档

  1. https://opentracing.io/docs/getting-started/
  2. https://opentracing.io/docs/overview/inject-extract/
  3. Inject and Extract

关于inject和extract

说下 inject和extract 在一个trace中任何SpanContext都可以被注入到一个叫做Carrier的东西里面。 Carrier是一个进程之间沟通的数据结构或者数据接口,它携带trace的状态信息从一个进程传到另一个。

注入(Inject):

  • 以内置的HTTP_HEADERS的carrier格式做例子说明, 它是用一个map,我们把SpanContext注入进去。
  • 一个协议传输时要带上carrier内容,比如http协议,我们把carrier作为header发送。

抽取(Extract):

  • 以内置的HTTP_HEADERS的carrier格式做例子说明,把从协议中拿到的关键数据是一个map。
  • 从map中可以抽取出SpanContext信息,然后它就可以作为创建子span的上下文。

安装开发环境

启动开发环境的jeager

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 \
    --restart=always \
    jaegertracing/all-in-one:1.15

win10环境下,可以在本地gitbash命令行或者ubuntu子系统命令行中运行上面命令,就启动了容器。基于docker desktop工具,可以看到启动的容器,并方便地启停。

访问 http://127.0.0.1:16686/search

jaeger如何确保http过来的链路连续?

todo

jaeger如果确保grpc client发送链路数据?

1、我自己摸索时的实现。

一般程序比如gin框架的,会写个middleware,把span存到context中。后面的函数调用只要从context中拿到span就能创建子span, 即一般程序中context都放span从而能形成span链路。

在调用grpc的client之前,需要从自己程序中的context中拿到span数据,创建一个子span,然后把数据注入到grpc要传输的md中,grpc发送的context中带上md。

对于gin框架,在最外层有middleware,会把包含span信息的context放到gin.Context中,存为key是SpanCTX{}的空结构体,所以从gin.Context能拿到含span的上下文。

// 调用grpc,为了链路追踪,需要传context
sendCtx := context.Background()
v, has := c.Get(jaeger.SpanCTX)
if has {
   if ctx, ok := v.(context.Context); ok {
      // jaeger,传输trace/span信息
      sendCtx = ctx
      subSpan, _ := opentracing.StartSpanFromContext(sendCtx, "calc auth service")
      m := map[string]string{}
      opentracing.GlobalTracer().Inject(subSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(m))

      md := metadata.New(m)
      sendCtx = metadata.NewOutgoingContext(ctx, md)
   }
}

// 调用client带上ctx
identify, err := rpcclient.GetAuthGrpcClient().Identify(sendCtx, &opencar.Token{
   Value: token,
})

2、opentracing提供了实现

在opentracing项目中,github.com\go-kit\kit\tracing\opentracing\grpc.go, 里面实现了几个函数,我们可以用ContextToGRPC(), 它从context中拿到span上下文写到grpc的md中。

ContextToGRPC

  • 从context中拿span,没拿到就直接返回ctx
  • 拿到了,就把里面span的context注入到md
  • 也就是调用的md中已经被写入了数据,用它作为grpc的md即可
// ContextToGRPC returns a grpc RequestFunc that injects an OpenTracing Span
// found in `ctx` into the grpc Metadata. If no such Span can be found, the
// RequestFunc is a noop.
func ContextToGRPC(tracer opentracing.Tracer, logger log.Logger) func(ctx context.Context, md *metadata.MD) context.Context {
   return func(ctx context.Context, md *metadata.MD) context.Context {
      if span := opentracing.SpanFromContext(ctx); span != nil {
         // There's nothing we can do with an error here.
         if err := tracer.Inject(span.Context(), opentracing.TextMap, metadataReaderWriter{md}); err != nil {
            logger.Log("err", err)
         }
      }
      return ctx
   }
}

jaeger如果确保grpc sever拿到链路数据?

1、我自己摸索时的实现。

当时并不知道extract怎么从md直接抽数据,所以自己实现把map[string][]string转为map[string]string

getJaeger := func(ctx context.Context, md metadata.MD) context.Context {
   // 从md中获取, 转为map[string]string{}
   m := map[string]string{}
   for k, v := range md {
      if len(v) > 0 {
         m[k] = v[0]
      }
   }
   // 抽取出jaeger信息转为
   spanContext, err := tracer.Extract(opentracing.TextMap, opentracing.TextMapCarrier(m))
   if err != nil {
      return ctx
   }
   span := opentracing.StartSpan("rpc", ext.RPCServerOption(spanContext))
   ctx = opentracing.ContextWithSpan(ctx, span)
   return ctx
}

2、opentracing提供了实现

在opentracing项目中,github.com/go-kit/kit/tracing/opentracing/grpc.go, 里面实现了几个函数,用于从grpc的md中提取数据生成包含span的context,还有把context信息写入grpc要传递的md中。

GRPCToContext函数

  • 通过trace的Extract从grpc的md中拿到spanContext数据。
  • 基于这个spanContext创建一个span。
  • 把这个span填入上下文context中,这样后续的业务处理函数从context就能拿到。
  • 如果能拿到spanContext,span就串联上了链路信息了。

// GRPCToContext returns a grpc RequestFunc that tries to join with an
// OpenTracing trace found in `req` and starts a new Span called
// `operationName` accordingly. If no trace could be found in `req`, the Span
// will be a trace root. The Span is incorporated in the returned Context and
// can be retrieved with opentracing.SpanFromContext(ctx).
func GRPCToContext(tracer opentracing.Tracer, operationName string, logger log.Logger) func(ctx context.Context, md metadata.MD) context.Context {
   return func(ctx context.Context, md metadata.MD) context.Context {
      var span opentracing.Span
      wireContext, err := tracer.Extract(opentracing.TextMap, metadataReaderWriter{&md})
      if err != nil && err != opentracing.ErrSpanContextNotFound {
         logger.Log("err", err)
      }
      span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext))
      return opentracing.ContextWithSpan(ctx, span)
   }
}

metadataReaderWriter实现

对于TextMap格式的carrier,opentracing提供了对应的TextMapReader接口设计,并提供了TextCarrier实现,但是它是对于map[string]string格式的数据处理的。我们从grpc的md拿数据,那么md的格式是map[string][]string, 数据格式不同,所以opentracing中就也实现了metadataReaderWriter。不过它就是提供给GRPCToContext函数用的,没有开放出来。

使用经验

gin项目按照controller、service、dao进行分层,每层都要加链路追踪吗?

在链路追踪 (如使用Jaeger、Zipkin等)的上下文中,理论上你可以在应用程序的任何层(包括controller层、service层、dao层等)添加追踪信息。然而,是否需要在每一层都显式地添加追踪信息,取决于你的具体需求和应用的架构。

一般原则

1. 关键路径和瓶颈:首先关注那些对性能或业务逻辑至关重要的路径和可能的瓶颈点。在这些地方添加跟踪信息可以更有效地帮助你定位问题。

2. 透明性: 尽可能让追踪的添加过程对你的业务逻辑保持透明。这意味着你不需要在业务代码

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值