设为首页 加入收藏

TOP

Go微服务全链路跟踪详解(三)
2019-09-23 11:11:32 】 浏览:169
Tags:服务 链路 跟踪 详解
客户端连接时,我们调用了一个为“OpenTracingClientInterceptor”的函数。 以下是“OpenTracingClientInterceptor”的部分代码,我从otgrpc¹¹包中的“client.go”中得到了它。它已经从Go context¹²获取了跟踪上下文并将其注入HTTP头,因此我们不再需要再次调用“inject”函数。

func OpenTracingClientInterceptor(tracer opentracing.Tracer, optFuncs ...Option) 
  grpc.UnaryClientInterceptor {
    ...
    ctx = injectSpanContext(ctx, tracer, clientSpan)
    ...
  }
  
  func injectSpanContext(ctx context.Context, tracer opentracing.Tracer, clientSpan opentracing.Span) 
    context.Context {
      md, ok := metadata.FromOutgoingContext(ctx)
      if !ok {
        md = metadata.New(nil)
      } else {
        md = md.Copy()
      }
      mdWriter := metadataReaderWriter{md}
      err := tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, mdWriter)
      // We have no better place to record an error than the Span itself :-/
      if err != nil {
        clientSpan.LogFields(log.String("event", "Tracer.Inject() failed"), log.Error(err))
      }
      return metadata.NewOutgoingContext(ctx, md)
}

在服务器端,我们还调用了一个函数“otgrpc.OpenTracingServerInterceptor”,其代码类似于客户端的“OpenTracingClientInterceptor”。它不是调用“inject”写入跟踪上下文,而是从HTTP头中提取(extract)跟踪上下文并将其放入Go上下文(Go context)中。 这就是我们不需要再次手动调用“extract()”的原因。 我们可以直接从Go上下文中提取跟踪上下文(opentracing.SpanFromContext(ctx))。 但对于其他基于HTTP的服务(如RESTFul服务), 情况就并非如此,因此我们需要写代码从服务器端的HTTP头中提取跟踪上下文。 当然,您也可以使用拦截器或过滤器。

跟踪库之间的互兼容性

你也许会问“如果我的程序使用Zipkin和OpenTracing而需要调用的第三方微服务使用OpenTracing与Jaeger,它们会兼容吗?"它看起来于我们之前询问的数据库问题类似,但实际上很不相同。对于数据库,因为应用程序和数据库在同一个进程中,它们可以共享相同的全局跟踪器,因此更容易解决。对于微服务,这种方式将不兼容。因为OpenTracing只标准化了跟踪接口,它没有标准化跟踪上下文。万维网联盟(W3C)正在制定跟踪上下文(trace context)¹?的标准,并于2019-08-09年发布了候选推荐标准。OpenTracing没有规定跟踪上下文的格式,而是把决定权留给了实现它的跟踪库。结果每个库都选择了自己独有的的格式。例如,Zipkin使用“X-B3-TraceId”作为跟踪ID,Jaeger使用“uber-trace-id”,因此使用OpenTracing并不意味着不同的跟踪库可以进行跨网互操作。 对于“Jaeger”来说有一个好处是你可以选择使用“Zipkin兼容性功能"¹³来生成Zipkin跟踪上下文, 这样就可以与Zipkin相互兼容了。对于其他情况,你需要自己进行手动格式转换(在“inject”和“extract”之间)。

全链路跟踪设计

尽量少写代码

一个好的全链路跟踪系统不需要用户编写很多跟踪代码。最理想的情况是你不需要任何代码,让框架或库负责处理它,当然这比较困难。 全链路跟踪分成三个跟踪级别:

  • 跨进程跟踪 (cross-process)(调用另一个微服务)

  • 数据库跟踪

  • 进程内部的跟踪 (in-process)(在一个函数内部的跟踪)

跨进程跟踪是最简单的。你可以编写拦截器或过滤器来跟踪每个请求,它只需要编写极少的编码。数据库跟踪也比较简单。如果使用我们上面讨论过的封装器(Wrapper),你只需要注册SQL驱动程序封装器(Wrapper)并将go-context(里面有跟踪上下文) 传入数据库函数。你可以使用依赖注入(Dependency Injection)这样就可以用比较少的代码来完成此操作。

进程内跟踪是最困难的,因为你必须为每个单独的函数编写跟踪代码。现在还没有一个很好的方法,可以编写一个通用的函数来跟踪应用程序中的每个函数(拦截器不是一个好选择,因为它需要每个函数的参数和返回都必须是一个泛型类型(interface {}))。幸运的是,对于大多数人来说,前两个级别的跟踪应该已经足够了。

有些人可能会使用服务网格(service mesh)来实现分布式跟踪,例如IstioLinkerd。它确实是一个好主意,跟踪最好由基础架构实现,而不是将业务逻辑代码与跟踪代码混在一起,不过你将遇到我们刚才谈到的同样问题。服务网格只负责跨进程跟踪,函数内部或数据库跟踪任然需要你来编写代码。不过一些服务网格可以通过提供与流行跟踪库的集成,来简化不同跟踪库跨网跟踪时的的上下文格式转换。

跟踪设计:

精心设计的跨度(span),服务名称(service name),标签(tag)能充分发挥全链路跟踪的作用,并使之简单易用。有关信息请阅读语义约定(Semantic Conventions)¹?。

将Trace ID记录到日志

将跟踪与日志记录集成是一个常见的需求,最重要的是将跟踪ID记录到整个调用链的日志消息中。 目前OpenTracing不提供访问traceID的方法。 你可以将“OpenTracing.SpanContext”转换为特定跟踪库的“SpanContext”(Zipkin和Jaeger都可以通过“SpanContext”访问traceID)或将“OpenTracing.SpanContext”转换为字符串并解析它以获取traceID。转换为字符串更好,因为它不会破坏程序的依赖关系。 幸运的是不久的将来你就不需要它了,因为OpenTracing将提供访问traceID的方法,请阅读这里

OpenTracing 和 OpenCensus

OpenCensus¹?不是另一个通用跟踪接口,它是一组库,可以用来与其他跟踪库集成以完成跟踪功能,因此它经常与OpenTracing进行比较。 那么它与OpenTracing兼容吗

首页 上一页 1 2 3 4 下一页 尾页 3/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇golang面对对象 下一篇GoLang中面向对象的三大特性

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目