golang-微服务go-micro之分布式链路追踪jaeger

1,741 阅读3分钟

微服务可观察性问题

当我们将一个集中式项目拆分成多个小的服务程序时,这时服务与服务之间的调用关系是错综复杂的,如何追踪服务之间的调用关系变得十分必要。分布式链路追踪不仅可以解决服务之间调用链路问题,还可以查看服务各个服务的调用时长,以此对相关服务程序做优化。此外,还可以集中收集各个服务的日志,等等功能。

Jaeger

在说Jaeger之前,先引入OpenTracing。OpenTracing是一个链路追踪的规范,Jaeger对此做了相关的实现。OpenTracing提出了链路追踪相关的数据模型。Opentracing后被纳入到OpenTelemetry。

image.png

Span

可以理解为一个服务调用(函数调用),拥有操作名称,开始时间,结束时间、Tags和Logs等属性。

Tracer

表示整个服务调用链路,由一个接着一个的Span所组成。程序中会用到的函数有StartSpan、Inject和Extract

SpanContext

表示上下文信息,用于连接Span。比如一个Span是另一个Span的Child或Follow

Jaeger程序

image.png

初次使用,我这里只是关心jaeger-client、jaeger-agent和UI。jaeger-client就是jaeger提供的客户端库,用来将追踪信息发送给jaeger-agent,然后jaeger的内部组件处理追踪信息,我们就可以直接在UI上查看。具体使用看官网。

image.png 上图是一个请求进入MYSERVICE,然后出的过程。在这个in和out的过程中,只有TracID被传播了,像一些分析数据(operation name,timing,tags和logs)会被直接通过client库发送给jaeger。为了使一个个Span连接起来,就要将TraceID好好传播。在MYSERVICE中需要先Extract出TraceID,然后再将TraceID给Inject进去,便能建立起追踪链路。

实验

这里使用了go-micro作为微服务框架, 使用了其链路追踪plugins。创建了三个服务,其中一个API网关,和两个业务服务。gin-api-gateway作为网关服务,其他服务分别是rand-service和user-service。下图是服务之间的依赖关系。

image.png

初始化jaeger

var Tracer opentracing.Tracer

func NewJaegerTracer(serviceName string, jaegerHostPort string) (opentracing.Tracer, io.Closer, error) {
   cfg := config.Configuration{
      ServiceName: serviceName,
      Sampler: &config.SamplerConfig{
         Type:  jaeger.SamplerTypeConst,
         Param: 1,
      },
      Reporter: &config.ReporterConfig{
         LogSpans:            true,
         BufferFlushInterval: 1 * time.Second,
         LocalAgentHostPort:  jaegerHostPort,
      },
   }
   var closer io.Closer
   var err error
   Tracer, closer, err = cfg.NewTracer(
      config.Logger(jaeger.StdLogger),
   )
   if err != nil {
      panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
   }
   opentracing.SetGlobalTracer(Tracer)
   return Tracer, closer, err
}

网关-Start

gin服务中添加中间件

func JaegerGatewayMiddleware(tracer opentracing.Tracer) gin.HandlerFunc {
   return func(ctx *gin.Context) {
      var md = make(metadata.Metadata, 1)
      opName := ctx.Request.URL.Path + "-" + ctx.Request.Method // 操作名称
      parentSpan := tracer.StartSpan(opName)
      defer parentSpan.Finish()
      injectErr := tracer.Inject(parentSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(md)) // 将TraceID注入到md中
      if injectErr != nil {
         logger.Fatalf("%s: Couldn't inject metadata", injectErr)
      }
      newCtx := metadata.NewContext(ctx.Request.Context(), md) // 利用context传递TraceID
      ctx.Request = ctx.Request.WithContext(newCtx)
      ctx.Next()
   }
}

各个RPC服务链接

直接调用go-micro提供的链路追踪plugins

import opentracingplugins "github.com/go-micro/plugins/v4/wrapper/trace/opentracing"
micro.WrapHandler(opentracingplugins.NewHandlerWrapper(tracer)),

内部主要实现, 先找提取父级Span的TraceID,启动本层的Span并携带这个TraceID,利用context传递下去。

func StartSpanFromContext(ctx context.Context, tracer opentracing.Tracer, name string, opts ...opentracing.StartSpanOption) (context.Context, opentracing.Span, error) {
   md, ok := metadata.FromContext(ctx)
   if !ok {
      md = make(metadata.Metadata)
   }

   // Find parent span.
   // First try to get span within current service boundary.
   // If there doesn't exist, try to get it from go-micro metadata(which is cross boundary)
   if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { 
      opts = append(opts, opentracing.ChildOf(parentSpan.Context()))
   } else if spanCtx, err := tracer.Extract(opentracing.TextMap, opentracing.TextMapCarrier(md)); err == nil {
      opts = append(opts, opentracing.ChildOf(spanCtx))
   }

   // allocate new map with only one element
   nmd := make(metadata.Metadata, 1)

   sp := tracer.StartSpan(name, opts...) 

   if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.TextMapCarrier(nmd)); err != nil {
      return nil, nil, err
   }

   for k, v := range nmd {
      md.Set(strings.Title(k), v)
   }

   ctx = opentracing.ContextWithSpan(ctx, sp)
   ctx = metadata.NewContext(ctx, md)
   return ctx, sp, nil
}

效果图

image.png

image.png

相关链接

The OpenTracing project

OpenTelemetry

Jaeger: open source, end-to-end distributed tracing (jaegertracing.io)

buddyxiao/go-micro-demo: 学习微服务 (github.com)