存在这样一种场景,当咱们进行微服务拆分后,一个请求将会通过多个服务处理以后再返回,这时,若是在请求的链路上某个服务出现故障时,排查故障将会比较困难.
咱们可能须要将请求通过的服务,挨个查看日志进行分析,当服务有几十上百个实例时,这无疑是可怕的.所以为了解决这种问题,调用链追踪应运而生.html
调用链追踪最早由googel在Dapper这篇论文中提出,OpenTracing主要定义了相关的协议以及接口,这样各个语言只要按照Opentracing的接口以及协议实现数据上报,那么调用信息就能统一被收集.java
如上图所示,接口可能首先通过web框架,而后调用auth服务,经过调用链,将请求通过的服务进行编号,统一收集起来,造成逻辑上的链路,这样,咱们就能够看到请求通过了哪些服务,从而造成服务依赖的拓扑.git
如上,总链路由每段链路组成,每段链路均表明通过的服务,耗时可用于分析系统瓶颈,当某个请求返回较慢时,能够经过排查某一段链路的耗时状况,从而分析是哪一个服务出现延时较高,今个到具体的服务中分析具体的问题.github
一次调用的链路,由TraceID惟一标志,如一次请求则一般为一个trace,trace由全部途径的span组成.web
没进过一个服务则将span,一样每一个span由spanID惟一标志.redis
span的标签,如一段span是调用redis的,而能够设置redis的标签,这样经过搜索redis关键字,咱们就能够查询出全部相关的span以及trace.app
附加的数据,由key:value组成,经过附加数据,能够给调用链更多的描述信息,不过考虑到传输问题,附加数据应该尽量少.框架
zipkin主要由java编写,经过各个语言的上报库实现将数据上报到collector,collector再将数据存储,并经过API提供给前段UI展现.函数
jaeger由go实现,由uber开发,目前是cloud native项目,流程与zipkin相似,增长jager-agent这样个组件,这个组件官方建议是每一个机器都部署一个,经过这个组件再将数据上报到collector存储展现,另外,里面作了对zipkin的适配,其实一开始他们用的也是zipkin,为毛后面要本身造轮子?见他们的解释. 连接
总的来讲二者都能基本知足opentracing的功能,具体的选择能够结合自身技术栈和癖好.
grpc集成opentracing并不难,由于grpc服务端以及调用端分别声明了UnaryClientInterceptor以及UnaryServerInterceptor两个回调函数,所以只须要重写这两个回调函数,并在重写的回调函数中调用opentracing接口进行上报便可.
初始化时传入重写后的回调函数,同时二选一初始化jager或者zipkin,而后你就能够开启分布式调用链追踪之旅了.
完整的代码见grpc-wrapper
// OpenTracingClientInterceptor rewrite client's interceptor with open tracing func OpenTracingClientInterceptor(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 { //从context中获取spanContext,若是上层没有开启追踪,则这里新建一个 //追踪,若是上层已经有了,测建立子span. var parentCtx opentracing.SpanContext if parent := opentracing.SpanFromContext(ctx); parent != nil { parentCtx = parent.Context() } cliSpan := tracer.StartSpan( method, opentracing.ChildOf(parentCtx), wrapper.TracingComponentTag, ext.SpanKindRPCClient, ) defer cliSpan.Finish() //将以前放入context中的metadata数据取出,若是没有则新建一个metadata md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.New(nil) } else { md = md.Copy() } mdWriter := MDReaderWriter{md} //将追踪数据注入到metadata中 err := tracer.Inject(cliSpan.Context(), opentracing.TextMap, mdWriter) if err != nil { grpclog.Errorf("inject to metadata err %v", err) } //将metadata数据装入context中 ctx = metadata.NewOutgoingContext(ctx, md) //使用带有追踪数据的context进行grpc调用. err = invoker(ctx, method, req, resp, cc, opts...) if err != nil { cliSpan.LogFields(log.String("err", err.Error())) } return err } }
//OpentracingServerInterceptor rewrite server's interceptor with open tracing func OpentracingServerInterceptor(tracer opentracing.Tracer) grpc.UnaryServerInterceptor { return func( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (resp interface{}, err error) { //从context中取出metadata md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.New(nil) } //从metadata中取出最终数据,并建立出span对象 spanContext, err := tracer.Extract(opentracing.TextMap, MDReaderWriter{md}) if err != nil && err != opentracing.ErrSpanContextNotFound { grpclog.Errorf("extract from metadata err %v", err) } //初始化server 端的span serverSpan := tracer.StartSpan( info.FullMethod, ext.RPCServerOption(spanContext), wrapper.TracingComponentTag, ext.SpanKindRPCServer, ) defer serverSpan.Finish() ctx = opentracing.ContextWithSpan(ctx, serverSpan) //将带有追踪的context传入应用代码中进行调用 return handler(ctx, req) } }
因为opentracing定义了相关的接口,而jaeger以及zipkin进行了相应的实现,所以这里可使用jaeger的也可使用zipkin进行上报.
jaeger服务主页信息
每条调用链信息