跟踪器|Go微服务全链路跟踪详解( 二 )


跟踪器|Go微服务全链路跟踪详解文章插图
怎样跟踪函数内部? 上面的图片没有告诉我们函数内部的跟踪细节 ,我们需要编写一些代码来获得它 。
以下是服务器端“get”函数 , 我们在其中添加了跟踪代码 。 它首先从上下文获取跨度(span) , 然后创建一个新的子跨度并使用我们刚刚获得的跨度作为父跨度 。 接下来 , 它执行一些操作(例如数据库查询) , 然后结束(mysqlSpan.Finish())子跨度 。
跟踪器|Go微服务全链路跟踪详解文章插图
以下是它运行后的图片 。 现在它在服务器端有一个新的跨度“db query user” 。
跟踪器|Go微服务全链路跟踪详解文章插图
以下是zipkin中的跟踪数据 。 你可以看到客户端从8.016ms开始 , 服务端也在同一时间启动 。 服务器端完成需要大约16ms 。
跟踪器|Go微服务全链路跟踪详解文章插图
怎样跟踪数据库? 怎样才能跟踪数据库内部的操作?首先 , 数据库驱动程序需要支持跟踪 , 另外你需要将跟踪器(tracer)传递到数据库函数中 。 如果数据库驱动程序不支持跟踪怎么办?现在已经有几个开源驱动程序封装器(Wrapper) , 它们可以封装任何数据库驱动程序并使其支持跟踪 。 其中一个是instrumentedsql[7]?(另外两个是luna-duclos/instrumentedsql[8]?和ocsql/driver.go[9]?) 。 我简要地看了一下他们的代码 , 他们的原理基本相同 。 它们都为底层数据库的每个函数创建了一个封装(Wrapper) , 并在每个数据库操作之前启动一个新的跨度 , 并在操作完成后结束跨度 。 但是所有这些都只封装了“database/sql”接口 , 这就意味着NoSQL数据库没有办法使用他们 。 如果你找不到支持你需要的NoSQL数据库(例如MongoDB)的OpenTracing的驱动程序 , 你可能需要自己编写一个封装(Wrapper),它并不困难 。
一个问题是“如果我使用OpenTracing和Zipkin而数据库驱动程序使用Openeracing和Jaeger , 那会有问题吗?"这其实不会发生 。 我上面提到的大部分封装都支持OpenTracing 。 在使用封装时 , 你需要注册封装了的SQL驱动程序 , 其中包含跟踪器 。 在SQL驱动程序内部 , 所有跟踪函数都只调用了OpenTracing的接口 , 因此它们甚至不知道底层实现是Zipkin还是Jaeger 。 现在使用OpenTarcing的好处终于体现出来了 。 在应用程序中创建全局跟踪器时(Global tracer) , 你需要决定是使用Zipkin还是Jaeger , 但这之后 , 应用程序或第三方库中的每个函数都只调用OpenTracing接口 , 已经与具体的跟踪库(Zipkin或Jaeger)没关系了 。
怎样跟踪服务调用? 假设我们需要在gRPC服务中调用另外一个微服务(例如RESTFul服务) , 该如何跟踪?
简单来说就是使用HTTP头作为媒介(Carrier)来传递跟踪信息(traceID) 。 无论微服务是gRPC还是RESTFul , 它们都使用HTTP协议 。 如果是消息队列(Message Queue) , 则将跟踪信息(traceID)放入消息报头中 。 (Zipkin B3-propogation[10]有“single header”和“multiple header”有两种不同类型的跟踪信息 , 但JMS仅支持“single header”)
一个重要的概念是“跟踪上下文(trace context)” , 它定义了传播跟踪所需的所有信息 , 例如traceID , parentId(父spanId)等 。 有关详细信息 , 请阅读跟踪上下文(trace context)[11]1? 。
OpenTracing提供了两个处理“跟踪上下文(trace context)”的函数:“extract(format , carrier)”和“inject(SpanContext , format , carrier)” 。 “extarct()”从媒介(通常是HTTP头)获取跟踪上下文 。 “inject”将跟踪上下文放入媒介 , 来保证跟踪链的连续性 。 以下是我从Zipkin获取的b3-propagation[12]图 。
跟踪器|Go微服务全链路跟踪详解文章插图
但是为什么我们没有在上面的例子中调用这些函数呢?让我们再来回顾一下代码 。 在客户端 , 在创建gRPC客户端连接时 , 我们调用了一个为“OpenTracingClientInterceptor”的函数 。 以下是“OpenTracingClientInterceptor”的部分代码 , 我从otgrpc[13]11包中的“client.go”中得到了它 。 它已经从Go context[14]12获取了跟踪上下文并将其注入HTTP头 , 因此我们不再需要再次调用“inject”函数 。
跟踪器|Go微服务全链路跟踪详解文章插图
在服务器端 , 我们还调用了一个函数“otgrpc.OpenTracingServerInterceptor” , 其代码类似于客户端的“OpenTracingClientInterceptor” 。 它不是调用“inject”写入跟踪上下文 , 而是从HTTP头中提取(extract)跟踪上下文并将其放入Go上下文(Go context)中 。 这就是我们不需要再次手动调用“extract()”的原因 。 我们可以直接从Go上下文中提取跟踪上下文(opentracing.SpanFromContext(ctx)) 。 但对于其他基于HTTP的服务(如RESTFul服务) ,情况就并非如此 , 因此我们需要写代码从服务器端的HTTP头中提取跟踪上下文 。 当然 , 您也可以使用拦截器或过滤器 。