【学习笔记】分布式追踪Tracing
在软件工程中,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/opentracing/opentracing-java
- 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/jaegertracing/jaeger),兼容OpenTracing API(支持Java语言:https://github.com/jaegertracing/jaeger-client-java)。
- 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-trace-java)是Datadog开源的一个java版本的APM(应用性能管理)客户端。它依赖了jaeger-client-java中的jaeger-core,采用字节码注入技术(JavaAgent)进行埋点,支持针对不同组件(http、kafka、jdbc等)进行插件化开发。
启动入口在AgentBootstrap的premain方法:
- AgentInstaller.installBytebuddyAgent:注册各种支持不同组件的埋点插件
- TracerInstaller.installGlobalTracer:注册一个全局的Tracer

1 public class AgentInstaller {
2
3 public static ResettableClassFileTransformer installBytebuddyAgent(final Instrumentation inst) {
4 AgentBuilder agentBuilder =
5 new AgentBuilder.Default()
6 .disableClassFormatChanges()
7 .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
8 .with(new RedefinitionLoggingListener())
9 .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)
10 .with(AgentTooling.poolStrategy())
11 .with(new TransformLoggingListener())
12 .with(new ClassLoadListener())
13 .with(AgentTooling.locationStrategy())
14 .ignore(any(), skipClassLoader())
15 .or(nameStartsWith("datadog.trace."))
16 .or(nameStartsWith("datadog.opentracing."))
17 .or(nameStartsWith("datadog.slf4j."))
18 .or(nameStartsWith("java.").and(not(nameStartsWith("java.util.concurrent."))))
19 .or(nameStartsWith("com.sun."))
20 .or(nameStartsWith("sun.").and(not(nameStartsWith("sun.net.www."))))
21 .or(nameStartsWith("jdk."))
22 .or(nameStartsWith("org.aspectj."))
23 .or(nameStartsWith("org.groovy."))
24 .or(nameStartsWith("com.p6spy."))
25 .or(nameStartsWith("org.slf4j."))
26 .or(nameContains("javassist"))
27 .or(nameContains(".asm."))
28 .or(nameMatches("com\\.mchange\\.v2\\.c3p0\\..*Proxy"));
29
30 for (final Instrumenter instrumenter : ServiceLoader.load(Instrumenter.class)) {
31 log.info("Loading instrumentation {}", instrumenter.getClass().getName());
32 agentBuilder = instrumenter.instrument(agentBuilder);
33 }
34 return agentBuilder.installOn(inst);
35 }
36 }
AgentInstaller
业务方可通过继承Instrumenter.Default进行插件化开发,以支持不同组件的埋点。

1 @AutoService(Instrumenter.class)
2 public class MDCInjectionInstrumentation extends Instrumenter.Default {
3
4 private static final String mdcClassName = "org.TMP.MDC".replaceFirst("TMP", "slf4j");
5
6 @Override
7 protected boolean defaultEnabled() {
8 return Config.get().isLogsInjectionEnabled();
9 }
10
11 @Override
12 public ElementMatcher<? super TypeDescription> typeMatcher() {
13 return named(mdcClassName);
14 }
15
16 @Override
17 public void postMatch(
18 final TypeDescription typeDescription,
19 final ClassLoader classLoader,
20 final JavaModule module,
21 final Class<?> classBeingRedefined,
22 final ProtectionDomain protectionDomain) {
23 if (classBeingRedefined != null) {
24 MDCAdvice.mdcClassInitialized(classBeingRedefined);
25 }
26 }
27
28 @Override
29 public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
30 return singletonMap(
31 isTypeInitializer(), MDCInjectionInstrumentation.class.getName() + "$MDCAdvice");
32 }
33
34 @Override
35 public String[] helperClassNames() {
36 return new String[]{LogContextScopeListener.class.getName()};
37 }
38
39 public static class MDCAdvice {
40 @Advice.OnMethodExit(suppress = Throwable.class)
41 public static void mdcClassInitialized(@Advice.Origin final Class mdcClass) {
42 try {
43 final Method putMethod = mdcClass.getMethod("put", String.class, String.class);
44 final Method removeMethod = mdcClass.getMethod("remove", String.class);
45 GlobalTracer.get().addScopeListener(new LogContextScopeListener(putMethod, removeMethod));
46 } catch (final NoSuchMethodException e) {
47 org.slf4j.LoggerFactory.getLogger(mdcClass).debug("Failed to add MDC span listener", e);
48 }
49 }
50 }
51 }
Instrumenter.Default
End
OpenTracing建立了一套标准,解决了不同的分布式追踪系统埋点API不兼容的问题(类似SLF4J);Uber开源的Jaeger提供一套完整的分布式追踪解决方案(兼容OpenTracing API),包括数据采集、数据持久化、数据展示;Datadog开源的dd-trace-java是一个APM client for Java(依赖jaeger-client-java),采用字节码注入技术(JavaAgent)进行埋点,支持针对不同组件进行插件化开发。
【学习笔记】分布式追踪Tracing的更多相关文章
- Redis学习笔记~分布式的Pub/Sub模式
回到目录 redis的客户端有很多,这次用它的pub/sub发布与订阅我选择了StackExchange.Redis,发布与订阅大家应该很清楚了,首先一个订阅者,订阅一个服务,服务执行一些处理程序(可 ...
- 【mq学习笔记-分布式篇】主从同步机制
核心类: 消息消费到达主服务器后需要将消息同步到从服务器,如果主服务器Broker宕机后,消息消费者可以从从服务器拉取消息. HAService:RocketMQ主从同步核心实现类 HAService ...
- [业界方案] 用SOFATracer学习分布式追踪系统Opentracing
[业界方案] 用SOFATracer学习分布式追踪系统Opentracing 目录 [业界方案] 用SOFATracer学习分布式追踪系统Opentracing 0x00 摘要 0x01 缘由 &am ...
- Redis学习笔记~目录
回到占占推荐博客索引 百度百科 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合). ...
- 学习笔记:The Log(我所读过的最好的一篇分布式技术文章)
前言 这是一篇学习笔记. 学习的材料来自Jay Kreps的一篇讲Log的博文. 原文很长,但是我坚持看完了,收获颇多,也深深为Jay哥的技术能力.架构能力和对于分布式系统的理解之深刻所折服.同时也因 ...
- 学习笔记:The Log(我所读过的最好的一篇分布式技术文章)
前言 这是一篇学习笔记. 学习的材料来自Jay Kreps的一篇讲Log的博文. 原文非常长.可是我坚持看完了,收获颇多,也深深为Jay哥的技术能力.架构能力和对于分布式系统的理解之深刻所折服.同一时 ...
- go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin)
目录 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin) zipkin使用demo 数据持久化 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin ...
- [业界方案]用Jaeger来学习分布式追踪系统Opentracing
[业界方案]用Jaeger来学习分布式追踪系统Opentracing 目录 [业界方案]用Jaeger来学习分布式追踪系统Opentracing 0x00 摘要 0x01 缘由 & 问题 1. ...
- Dynamic CRM 2013学习笔记(七)追踪、监控及性能优化
本文将介绍CRM的三个内容追踪.监控及性能优化.追踪是CRM里一个很有用的功能,它能为我们的CRM调试或解决错误.警告提供有价值的信息:我们可以用window的性能监控工具来了解CRM的性能状况:最后 ...
随机推荐
- redis 客户端
输入缓冲区: 客户端状态的输入缓冲区用于保存客户端发送的命令请求: typedef struct redisClient{ //... sds querybuf; //... }redisClient ...
- 03Python网络编程系列之服务端
# 这里边是一个定义了服务端的一系列函数,是Python网络编程这本书第七章的第一个例子.# 这是供后边函数进行调用了,然后我们来进行研究网络的单线程编程,多线程编程.异步网络编程等.# 导入网络编程 ...
- SpringBoot第十二集:度量指标监控与异步调用(2020最新最易懂)
SpringBoot第十二集:度量指标监控与异步调用(2020最新最易懂) Spring Boot Actuator是spring boot项目一个监控模块,提供了很多原生的端点,包含了对应用系统的自 ...
- CSP-2020 退役记
CSP-2020 游记 第2次参加CSP-- Day -5~-7 每天笔试+机试 Day -8~-9 在家放松(写作业) Day 0 鸡鸭月考 Day 1 9:30以前 愉快的在别人月考的时候离开鸡鸭 ...
- 「懒惰的美德」我用 python 写了个自动生成给文档生成索引的脚本
我用 python 写了一个自动生成索引的脚本 简介:为了刷算法题,建了一个 GitHub仓库:PiperLiu / ACMOI_Journey,记录自己的刷题轨迹,并总结一下方法.心得.想到一个需求 ...
- 最常用的分布式ID解决方案,你知道几个
一.分布式ID概念 说起ID,特性就是唯一,在人的世界里,ID就是身份证,是每个人的唯一的身份标识.在复杂的分布式系统中,往往也需要对大量的数据和消息进行唯一标识.举个例子,数据库的ID字段在单体的情 ...
- C#9.0新特性之四:顶级程序语句(Top-Level Programs)
1 背景与动机 通常,如果只想用C#在控制台上打印一行"Hello World!",这可不是Console.WriteLine("Hello World!"); ...
- PHP代码审计分段讲解(12)
28题 <!DOCTYPE html> <html> <head> <title>Web 350</title> <style typ ...
- 题解-Little C Loves 3 III
Little C Loves 3 III 给定 \(n\) 和序列 \(a_0,a_1,\dots,a_{2^n-1}\) 和 \(b_0,b_1,\dots,b_{2^n-1}\),求序列 \(c_ ...
- elastic-job分布式调度与zookeeper的简单应用
一.对分布式调度的理解 调度->定时任务,分布式调度->在分布式集群环境下定时任务这件事 Elastic-job(当当⽹开源的分布式调度框架) 1 定时任务的场景 定时任务形式:每隔⼀定时 ...