关于java:学习笔记分布式追踪Tracing

52次阅读

共计 6971 个字符,预计需要花费 18 分钟才能阅读完成。

在软件工程中,Tracing 指应用特定的日志记录程序的执行信息,与之相近的还有两个概念,它们别离是 Logging 和 Metrics。

  • Logging:用于记录离散的事件,蕴含程序执行到某一点或某一阶段的详细信息,比方,应用程序的调试(debug)信息或谬误(error)信息。它是咱们诊断问题的根据。
  • Metrics:用于记录可聚合的数据,且通常是固定类型的时序数据,每个都是一个逻辑计量单元,或者一个时间段内的柱状图,比方,队列的以后深度能够被定义为一个计量单元,在写入或读取时被更新统计;输出 HTTP 申请的数量能够被定义为一个计数器,用于简略累加;申请的执行工夫能够被定义为一个柱状图,在指定工夫片上更新和统计汇总。
  • Tracing:用于记录单次申请范畴内的解决信息,其中包含服务调用和解决时长等,比方,一次调用近程服务的 RPC 执行过程;一次理论的 SQL 查问语句;一次 HTTP 申请的业务性 ID。它是咱们排查零碎性能问题的利器。

零碎架构从单体转变为微服务当前,一次申请往往波及到多个服务之间的调用。随着服务数量的增多和外部调用链的复杂化,仅凭借日志和性能监控很难做到“See the Whole Picture”,在进行问题排查或是性能剖析的时候,无异于盲人摸象。

分布式追踪零碎(Tracing)旨在剖析申请背地调用了哪些服务,服务的调用程序、耗时、谬误起因等,帮忙开发者直观剖析申请链路,疾速定位性能瓶颈,逐步优化服务间依赖,也有助于开发者从更宏观的角度更好地了解整个分布式系统。

早在 2005 年,Google 就在外部部署了一套分布式追踪零碎 Dapper,并发表了一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,论述了该分布式追踪零碎的设计和实现,能够视为分布式追踪畛域的鼻祖。随后各大厂商纷纷落地了一些优良的分布式追踪零碎,比方 Jaeger(Uber)、Zipkin(twitter)、X-ray(AWS)、SkyWalking 等,但各家的分布式追踪计划很可能是互不兼容的,于是诞生了 OpenTracing。

1. OpenTracing

OpenTracing 是一个轻量级的标准化层,它位于 < 应用程序 / 类库 > 和 < 追踪或日志分析程序 > 之间,通过提供平台无关、厂商无关的 API,使得开发人员可能不便的增加(或更换)追踪零碎的实现,解决不同的分布式追踪零碎 API 不兼容的问题,也使得在通用代码库减少对分布式追踪的反对成为可能。

  • 后盾无关的一套接口 ,被跟踪的服务只须要调用这套接口,就能够被任何实现这套接口的跟踪后盾(比方 Zipkin, Jaeger 等等)反对,而作为一个跟踪后盾,只有实现了个这套接口,就能够跟踪到任何调用这套接口的服务。
  • 标准化了对跟踪最小单位 Span 的治理 :定义了开始 Span,完结 Span 和记录 Span 耗时的 API。
  • 标准化了过程间跟踪数据传递的形式 :定义了一套 API 不便跟踪数据的传递。
  • 标准化了过程内以后 Span 的治理 :定义了存储和获取以后 Span 的 API。
  • 不对编码定规范 :不对过程间传递的跟踪数据的编码定规范,不对向后盾发送的跟踪数据的编码定规范,让跟踪后盾本人决定最适宜他们的编码方式。

OpenTracing 已进入 CNCF,正在为寰球的分布式追踪,提供对立的概念和数据规范,反对多种语言:https://github.com/opentracing。其中,OpenTracing API for Java:https://github.com/opentracin…

  • opentracing-api,是一个纯正的 API 没有任何依赖
  • opentracing-noop,实现了 API,然而是空实现什么也不干,依赖 opentracing-api
  • opentracing-util,蕴含了一个 GlobalTracer 和基于 Thread_local 的简略实现 ScopeManager,依赖 opentracing-api、opentracing-noop
  • opentracing-mock,mock 测试,蕴含一个简略的 MockTracer,将数据存储进内存,依赖 opentracing-api、opentracing-noop、opentracing-util
  • opentracing-testbed,用于测试和尝试新个性

OpenTracing 数据模型中有三个重要的互相关联的类型,别离是 Tracer,Span 和 SpanContext。

  • Trace:一个残缺申请链路
  • Span:一次调用过程 (须要有开始工夫和完结工夫)
  • SpanContext:Trace 的全局上下文信息,如外面有 traceId

Trace

一个 Trace 代表一个事务或者流程在(分布式)零碎中的执行过程。一条 Trace(调用链)能够被认为是一个由多个 Span 组成的有向无环图(DAG),通过归属于此调用链的 Span 来隐性的定义。

Tracer 接口用来创立 Span,以及解决如何解决 Inject(serialize) 和 Extract (deserialize),用于跨过程边界传递。

Span

一个 Span 代表零碎中具备开始工夫和执行时长的逻辑运行单元。Span 之间通过嵌套或者顺序排列建设逻辑因果关系。Span 能够被了解为一次办法调用, 一个程序块的调用, 或者一次 RPC/ 数据库拜访,只有是一个具备残缺工夫周期的程序拜访,都能够被认为是一个 span。每个 Span 蕴含以下的状态:

  • An operation name,操作名称 
  • A start timestamp,起始工夫
  • A finish timestamp,完结工夫
  • Span Tag,一组键值对形成的 Span 标签汇合。键值对中,键必须为 string,值能够是字符串,布尔,或者数字类型
  • Span Log,一组 span 的日志汇合。每次 log 操作蕴含一个键值对,以及一个工夫戳。键值对中,键必须为 string,值能够是任意类型
  • SpanContext,Span 上下文对象
  • References,Span 间关系,目前定义了两种关系:ChildOf(父子,父级 span 某种程度上取决于子 span)和 FollowsFrom(追随,父级节点不以任何形式仍然他们子节点的执行后果)

SpanContext

Span 上下文对象,代表逾越过程边界,传递到上级 span 的状态。每一个 SpanContext 蕴含以下状态:

  • 任何一个 OpenTracing 的实现,都须要将以后调用链的状态(例如:trace 和 span 的 id),依赖一个独特的 Span 去跨过程边界传输
  • Baggage Items,Trace 的随行数据,是一个键值对汇合,它存在于 trace 中,也须要跨过程边界传输

OpenTracing 的使用者仅仅须要,在创立 span、向传输协定 Inject(注入)和从传输协定中 Extract(提取)时,应用 SpanContext 和 References。

2. Jaeger

Jaeger 是 Uber 开源的一款分布式追踪零碎(https://github.com/jaegertrac…),兼容 OpenTracing API(反对 Java 语言:https://github.com/jaegertrac…)。

  • jaeger-client:Jaeger 的客户端,实现了 OpenTracing 的 API,反对支流编程语言。客户端间接集成在应用程序中,把 trace 信息按指定的采样策略传递给 jaeger-agent,这个过程通常被称为埋点。
  • jaeger-agent:一个监听在 UDP 端口上接管 trace 信息的网络守护过程,会将数据批量发送给 jaeger-collector。它被设计成一个根底组件,部署到所有的宿主机上,agent 将 client 和 collector 解耦,为 client 屏蔽路由和发现 collector 的细节。
  • jaeger-collector:负责接管 jaeger-agent 发送来的数据,而后异步解决,最终将数据存储到 DB 中。它被设计成无状态的组件,因而能够同时运行任意数量的 jaeger-collector。
  • jaeger-query:接管查问申请,而后从 DB 中检索 trace 信息并通过 UI 进行展现。Query 是无状态的,能够启动多个实例,把它们部署在 nginx 这样的负载均衡器前面。
  • jaeger-ingester:中文名称“摄食者”,从 kafka 读取数据而后写到 jaeger 的后端存储,比方 Cassandra 和 Elasticsearch。

分布式追踪零碎大体分为三个局部,数据采集、数据长久化、数据展现。

  • 数据采集是指在代码中埋点,设置申请中要上报的阶段,以及设置以后记录的阶段隶属于哪个下级阶段。
  • 数据长久化则是指将上报的数据落盘存储,例如 Jaeger 就反对多种存储后端,可选用 Cassandra 或者 Elasticsearch。
  • 数据展现则是前端依据 TraceId 查问与之关联的申请阶段,并在界面上出现。

3. dd-trace-java

dd-trace-java(https://github.com/DataDog/dd…)是 Datadog 开源的一个 java 版本的 APM(利用性能治理)客户端。它依赖了 jaeger-client-java 中的 jaeger-core,采纳字节码注入技术(JavaAgent)进行埋点,反对针对不同组件(http、kafka、jdbc 等)进行插件化开发。

启动入口在 AgentBootstrap 的 premain 办法:

  • AgentInstaller.installBytebuddyAgent:注册各种反对不同组件的埋点插件
  • TracerInstaller.installGlobalTracer:注册一个全局的 Tracer
public class AgentInstaller {public static ResettableClassFileTransformer installBytebuddyAgent(final Instrumentation inst) {AgentBuilder agentBuilder = new AgentBuilder.Default()
            .disableClassFormatChanges()
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .with(new RedefinitionLoggingListener())
            .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)
            .with(AgentTooling.poolStrategy())
            .with(new TransformLoggingListener())
            .with(new ClassLoadListener())
            .with(AgentTooling.locationStrategy())
            .ignore(any(), skipClassLoader())
            .or(nameStartsWith("datadog.trace."))
            .or(nameStartsWith("datadog.opentracing."))
            .or(nameStartsWith("datadog.slf4j."))
            .or(nameStartsWith("java.").and(not(nameStartsWith("java.util.concurrent."))))
            .or(nameStartsWith("com.sun."))
            .or(nameStartsWith("sun.").and(not(nameStartsWith("sun.net.www."))))
            .or(nameStartsWith("jdk."))
            .or(nameStartsWith("org.aspectj."))
            .or(nameStartsWith("org.groovy."))
            .or(nameStartsWith("com.p6spy."))
            .or(nameStartsWith("org.slf4j."))
            .or(nameContains("javassist"))
            .or(nameContains(".asm."))
            .or(nameMatches("com.mchange.v2.c3p0..*Proxy"));
 
        for (final Instrumenter instrumenter : ServiceLoader.load(Instrumenter.class)) {log.info("Loading instrumentation {}", instrumenter.getClass().getName());
            agentBuilder = instrumenter.instrument(agentBuilder);
        }
        return agentBuilder.installOn(inst);
    }
}

业务方可通过继承 Instrumenter.Default 进行插件化开发,以反对不同组件的埋点。

@AutoService(Instrumenter.class)
public class MDCInjectionInstrumentation extends Instrumenter.Default {private static final String mdcClassName = "org.TMP.MDC".replaceFirst("TMP", "slf4j");
    
    @Override
    protected boolean defaultEnabled() {return Config.get().isLogsInjectionEnabled();}
    
    @Override
    public ElementMatcher<? super TypeDescription> typeMatcher() {return named(mdcClassName);
    }
    
    @Override
    public void postMatch(
            final TypeDescription typeDescription,
            final ClassLoader classLoader,
            final JavaModule module,
            final Class<?> classBeingRedefined,
            final ProtectionDomain protectionDomain) {if (classBeingRedefined != null) {MDCAdvice.mdcClassInitialized(classBeingRedefined);
        }
    }
    
    @Override
    public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
        return singletonMap(isTypeInitializer(), MDCInjectionInstrumentation.class.getName() + "$MDCAdvice");
    }
    
    @Override
    public String[] helperClassNames() {return new String[]{LogContextScopeListener.class.getName()};
    }
 
    public static class MDCAdvice {@Advice.OnMethodExit(suppress = Throwable.class)
        public static void mdcClassInitialized(@Advice.Origin final Class mdcClass) {
            try {final Method putMethod = mdcClass.getMethod("put", String.class, String.class);
                final Method removeMethod = mdcClass.getMethod("remove", String.class);
                GlobalTracer.get().addScopeListener(new                 LogContextScopeListener(putMethod, removeMethod));
            } catch (final NoSuchMethodException e) {org.slf4j.LoggerFactory.getLogger(mdcClass).debug("Failed to add MDC span listener", e);
            }
        }
    }
}

End

OpenTracing 建设了一套规范,解决了不同的分布式追踪零碎埋点 API 不兼容的问题(相似 SLF4J);Uber 开源的 Jaeger 提供一套残缺的分布式追踪解决方案(兼容 OpenTracing API),包含数据采集、数据长久化、数据展现;Datadog 开源的 dd-trace-java 是一个 APM client for Java(依赖 jaeger-client-java),采纳字节码注入技术(JavaAgent)进行埋点,反对针对不同组件进行插件化开发。

正文完
 0