分布式追踪方案

分布式追踪方案.md

参考文档

  1. OpenTracing官网
  2. OpenTracing标准(中文版)
  3. OpenTracing:开放式分布式追踪规范
  4. 开放分布式追踪(OpenTracing)入门与 Jaeger 实现
  5. service mesh istio微服务实验之监控日志与可视化
  6. Istio是啥?一文带你彻底了解!
  7. 【go-micro实践】jaeger分布式链路追踪
  8. 通过 Jaeger 上报 Java 应用数据
  9. Configuring Jaeger in Spring application
  10. Jaeger Tracing(Open Tracing) 遇到线程池哑火
  11. 一个java.lang.NoSuchMethodError问题的解决
  12. Spring MVC【入门】就这一篇!
  13. 详述 IntelliJ IDEA 创建 Maven 项目及设置 java 源目录的方法

为什么需要追踪

容器、Serverless 编程方式的诞生极大提升了软件交付与部署的效率。在架构的演化过程中,可以看到两个变化:
在这里插入图片描述

  • 应用架构开始从单体系统逐步转变为微服务,其中的业务逻辑随之而来就会变成微服务之间的调用与请求。
  • 资源角度来看,传统服务器这个物理单位也逐渐淡化,变成了看不见摸不到的虚拟资源模式

从以上两个变化可以看到这种弹性、标准化的架构背后,原先运维与诊断的需求也变得越来越复杂。为了应对这种变化趋势,诞生一系列面向 DevOps 的诊断与分析系统,包括集中式日志系统(Logging),集中式度量系统(Metrics)和分布式追踪系统(Tracing)。

Tracing 的诞生

Tracing 是在90年代就已出现的技术。但真正让该领域流行起来的还是源于 Google 的一篇论文"Dapper, a Large-Scale Distributed Systems Tracing Infrastructure",而另一篇论文"Uncertainty in Aggregate Estimates from Sampled Distributed Traces"中则包含关于采样的更详细分析。论文发表后一批优秀的 Tracing 软件孕育而生,比较流行的有:

  • Dapper(Google) : 各 tracer 的基础
  • StackDriver Trace (Google)
  • Zipkin(twitter)
  • Appdash(golang)
  • 鹰眼(taobao)
  • 谛听(盘古,阿里云云产品使用的Trace系统)
  • 云图(蚂蚁Trace系统)
  • sTrace(神马)
  • X-ray(aws)

分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点,数据存储、查询展示。

下图是一个分布式调用的例子,客户端发起请求,请求首先到达负载均衡器,接着经过认证服务,计费服务,然后请求资源,最后返回结果。

在这里插入图片描述

数据被采集存储后,分布式追踪系统一般会选择使用包含时间轴的时序图来呈现这个 Trace。
在这里插入图片描述

Opentracing规范介绍

OpenTracing是一个跨编程语言的标准。

OpenTracing数据模型

Trace定义

OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。

注: Span,可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span.在此译本中,为了便于理解,Span和其他标准内声明的词汇,全部不做名词翻译。

例如:下面的示例Trace就是由8个Span组成:

Trace因果关系图

单个Trace中,span间的因果关系


        [Span A]  ←←←(the root span)
            |
     +------+------+
     |             |
 [Span B]      [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)
     |             |
 [Span D]      +---+-------+
               |           |
           [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                       ↑
                                       ↑
                                       ↑
                         (Span G 在 Span F 后被调用, FollowsFrom)

Tarce时序图

    ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|> time

    [Span A···················································]
    [Span B··············································]
        [Span D··········································]
        [Span C········································]
            [Span E·······]        [Span F··] [Span G··] [Span H··]
span介绍
span状态

每个Span包含以下的状态:

  • An operation name,操作名称

  • A start timestamp,起始时间

  • A finish timestamp,结束时间

  • Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型。

  • Span Log,一组span的日志集合。
    每次log操作包含一个键值对,以及一个时间戳。
    键值对中,键必须为string,值可以是任意类型。
    但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型。

  • SpanContext,Span上下文对象 (下面会详细说明)

  • References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)
    每一个SpanContext包含以下状态:

  • 任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输

  • Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输

span间关系

一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)。这两种关系明确的给出了两个父子关系的Span的因果模型。 将来,OpenTracing可能提供非因果关系的span间关系。(例如:span被批量处理,span被阻塞在同一个队列中,等等)。

ChildOf 引用: 一个span可能是一个父级span的孩子,即"ChildOf"关系。在"ChildOf"引用关系下,父级span某种程度上取决于子span。下面这些情况会构成"ChildOf"关系:

  • 一个RPC调用的服务端的span,和RPC服务客户端的span构成ChildOf关系
  • 一个sql insert操作的span,和ORM的save方法的span构成ChildOf关系
  • 很多span可以并行工作(或者分布式工作)都可能是一个父级的span的子项,他会合并所有子span的执行结果,并在指定期限内返回

下面都是合理的表述一个"ChildOf"关系的父子节点关系的时序图。

    [-Parent Span---------]
         [-Child Span----]

    [-Parent Span--------------]
         [-Child Span A----]
          [-Child Span B----]
        [-Child Span C----]
         [-Child Span D---------------]
         [-Child Span E----]

FollowsFrom 引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子span和父span之间是"FollowsFrom"的因果关系。"FollowsFrom"关系可以被分为很多不同的子类型,未来版本的OpenTracing中将正式的区分这些类型

下面都是合理的表述一个"FollowFrom"关系的父子节点关系的时序图。

    [-Parent Span-]  [-Child Span-]


    [-Parent Span--]
     [-Child Span-]


    [-Parent Span-]
                [-Child Span-]

Opentracing API

OpenTracing标准中有三个重要的相互关联的类,分别是Tracer, SpanSpanContext。下面,我们分别描述每种类的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法。

当我们讨论“可选”参数时,需要强调的是,不同的语言针对可选参数有不同理解,概念和实现方式 。例如,在Go中,我们习惯使用"functional Options",而在Java中,我们可能使用builder模式。

Tracer

Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递。它具有如下官方能力:

创建一个新Span

必填参数

  • operation name, 操作名, 一个具有可读性的字符串,代表这个span所做的工作(例如:RPC方法名,方法名,或者一个大型计算中的某个阶段或子任务)。操作名应该是一个抽象、通用,明确、具有统计意义的名称。因此,"get_user" 作为操作名,比 "get_user/314159"更好。

例如,假设一个获取账户信息的span会有如下可能的名称:

操作名指导意见
get太抽象
get_account/792太明确
get_account正确的操作名,关于account_id=792的信息应该使用Tag操作

可选参数

  • 零个或者多个关联(references)的SpanContext,如果可能,同时快速指定关系类型,ChildOf 还是 FollowsFrom
  • 一个可选的显性传递的开始时间;如果忽略,当前时间被用作开始时间。
  • 零个或者多个tag

返回值,返回一个已经启动Span实例(已启动,但未结束。译者注:英语上started和finished理解容易混淆)

SpanContext上下文Inject(注入)到carrier

必填参数

  • **SpanContext**实例
  • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何对SpanContext进行编码放入到carrier中。
  • carrier,根据format确定。Tracer实现根据format声明的格式,将SpanContext序列化到carrier对象中。
SpanContext上下文从carrier中Extract(提取)

必填参数

  • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何从carrier中解码SpanContext
  • carrier,根据format确定。Tracer实现根据format声明的格式,从carrier中解码SpanContext

返回值,返回一个SpanContext实例,可以使用这个SpanContext实例,通过Tracer创建新的Span

注意,对于Inject(注入)和Extract(提取),format是必须的。

Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中SpanContext是如何编码的。所有的Tracer实现,都必须支持下面的format

  • Text Map: 基于字符串:字符串的map,对于key和value不约束字符集。
  • HTTP Headers: 适合作为HTTP头信息的,基于字符串:字符串的map。(RFC 7230.在工程实践中,如何处理HTTP头具有多样性,强烈建议tracer的使用者谨慎使用HTTP头的键值空间和转义符)
  • Binary: 一个简单的二进制大对象,记录SpanContext的信息。
Span

Span结束后(span.finish()),除了通过Span获取SpanContext外,下列其他所有方法都不允许被调用。

获取SpanSpanContext

不需要任何参数。

返回值Span构建时传入的SpanContext。这个返回值在Span结束后(span.finish()),依然可以使用。

复写操作名(operation name)

必填参数

  • 新的操作名operation name,覆盖构建Span时,传入的操作名。
结束Span

可选参数

  • 一个明确的完成时间;如果省略此参数,使用当前时间作为完成时间。
Span设置tag

必填参数

  • tag key,必须是string类型
  • tag value,类型为字符串,布尔或者数字

注意,OpenTracing标准包含**“standard tags,标准Tag”**,此文档中定义了Tag的标准含义。

Log结构化数据

必填参数

  • 一个或者多个键值对,其中键必须是字符串类型,值可以是任意类型。某些OpenTracing实现,可能支持更多的log值类型。

可选参数

  • 一个明确的时间戳。如果指定时间戳,那么它必须在span的开始和结束时间之内。

注意,OpenTracing标准包含**“standard log keys,标准log的键”**,此文档中定义了这些键的标准含义。

设置一个baggage(随行数据)元素

Baggage元素是一个键值对集合,将这些值设置给给定的SpanSpanSpanContext,以及所有和此Span有直接或者间接关系的本地Span 也就是说,baggage元素随trace一起保持在带内传递。(译者注:带内传递,在这里指,随应用程序调用过程一起传递)

Baggage元素具有强大的功能,使得OpenTracing能够实现全栈集成(例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统),同时他也会产生巨大的开销,请小心使用此特性。

再次强调,请谨慎使用此特性。每一个键值都会被拷贝到每一个本地和远程的下级相关的span中,因此,总体上,他会有明显的网络和CPU开销。

必填参数

  • baggage key, 字符串类型
  • baggage value, 字符串类型
获取一个baggage元素

必填参数

  • baggage key, 字符串类型

返回值,相应的baggage value,或者可以标识元素值不存在的返回值(译者注:如Null)。

SpanContext

相对于OpenTracing中其他的功能,SpanContext更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。
OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContextreferences

OpenTracing要求,SpanContext不可变的,目的是防止由于Span的结束和相互关系,造成的复杂生命周期问题。

遍历所有的baggage元素

遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的SpanContext实例,高效的遍历所有的baggage元素

NoopTracer

所有的OpenTracing API实现,必须提供某种方式的NoopTracer实现。NoopTracer可以被用作控制或者测试时,进行无害的inject注入(等等)。例如,在 OpenTracing-Java实现中,NoopTracer在他自己的模块中。

可选 API 元素

有些语言的OpenTracing实现,为了在串行处理中,传递活跃的SpanSpanContext,提供了一些工具类。例如,opentracing-go中,通过context.Context机制,可以设置和获取活跃的Span

方案

1.jaeger
2.zipkin

jaeger介绍

Jaeger 是 Uber 推出的一款开源分布式追踪系统,兼容 OpenTracing API。

Jaeger 架构

在这里插入图片描述

如上图所示,Jaeger 主要由以下几部分组成。

  • Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 - jaeger-agent。
  • Agent - 它是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。它被设计成一个基础组件,部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。
  • Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector。
  • Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandra、elastic search。
  • Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。

spring中添加jaeger客户端方法介绍

手动埋点

1.打开pom.xml,添加对jaeger客户端的依赖

    <dependencies>
        <dependency>
            <groupId>io.opentracing.contrib</groupId>
            <artifactId>opentracing-spring-cloud-starter</artifactId>
            <version>0.3.2</version>
        </dependency>
        <dependency>
            <groupId>io.jaegertracing</groupId>
            <artifactId>jaeger-client</artifactId>
            <version>0.35.0</version>
        </dependency>
    </dependencies>

2.配置初始化参数并创建 Tracer(spring配置)。
Tracer 对象可以用来创建 Span 对象(记录分布式操作时间)、跨机器透传数据(Extract/Inject 方法),或设置当前 Span(activeSpan)。Tracer 对象还配置了上报数据的网关地址、本机 IP、采样率、服务名等数据。您可以通过调整采样率来减少因上报数据产生的开销。

    io.jaegertracing.Configuration config = new io.jaegertracing.Configuration("manualDemo");
    io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration();
    // 将 <endpoint> 替换为控制台概览页面上相应客户端和相应地域的接入点
    sender.withEndpoint("<endpoint>");
    config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(1));
    config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000));
    GlobalTracer.register(config.getTracer());

3.记录请求数据。

    // 获取tracer
    Tracer tracer = GlobalTracer.get();
    // 创建span
    Span span = tracer.buildSpan("parentSpan").withTag("myTag", "spanFrist").start();
    // try 结束后自动scope自动close,还原activeSpan
    try (Scope ignored = tracer.activateSpan(span)) {
        tracer.activeSpan().setTag("methodName", "testTracing");
        // .... 业务逻辑
        secondBiz();
    } catch (Exception e) {
        //记录异常信息
        TracingHelper.onError(e, span);
        throw e;
    } finally {
        span.finish();
    }

4.(可选)为了方便排查问题,您可以为某个记录添加一些自定义标签(Tag),例如记录是否发生错误、请求的返回值等。

    tracer.activeSpan().setTag("methodName", "testCall");

5.在分布式系统中发送 RPC 请求时会带上 Tracing 数据,包括 TraceId、ParentSpanId、SpanId、Sampled 等。您可以在 HTTP 请求中使用 Extract/Inject 方法在 HTTP Request Headers 上透传数据。总体流程如下:

a.在客户端调用 Inject 方法传入 Context 信息。

    private void attachTraceInfo(Tracer tracer, Span span, final Request request) {
        tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMap() {
            @Override
            public void put(String key, String value) {
                request.setHeader(key, value);
            }
            @Override
            public Iterator<Map.Entry<String, String>> iterator() {
                throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
            }
        });
    }

b.在服务端调用 Extract 方法解析 Context 信息。

    protected Span extractTraceInfo(Request request, Tracer tracer) {
        Tracer.SpanBuilder spanBuilder = tracer.buildSpan("/api/xtrace/test03");
        try {
            SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(request.getAttachments()));
            if (spanContext != null) {
                spanBuilder.asChildOf(spanContext);
            }
        } catch (Exception e) {
            spanBuilder.withTag("Error", "extract from request fail, error msg:" + e.getMessage());
        }
        return spanBuilder.start();
    }
组件埋点

目前 OpenTracing 社区已有许多组件可支持各种 Java 框架,例如:

请下载demo工程,进入 springboot-jaeger-demo 目录,并按照 Readme 的说明运行程序。
1.打开pom.xml,添加对jaeger客户端的依赖

    <dependency>
        <groupId>io.opentracing.contrib</groupId>
        <artifactId>opentracing-spring-cloud-starter</artifactId>
        <version>0.3.2</version>
    </dependency>
    <dependency>
        <groupId>io.jaegertracing</groupId>
        <artifactId>jaeger-client</artifactId>
        <version>0.35.0</version>
    </dependency>

2.添加 OpenTracing Tracer Bean

    @Bean
    public io.opentracing.Tracer tracer() {
        io.jaegertracing.Configuration config = new io.jaegertracing.Configuration("springFrontend");
        io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration();
        // 将 <endpoint> 替换为控制台概览页面上相应客户端和相应地域的接入点
        sender.withEndpoint("<endpoint>");
        config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(1));
        config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000));
        return config.getTracer();
    }

效果图
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

go中添加jaeger客户端方法介绍

Demo(https://github.com/david-zh-cn/JaegerDemo)

里面有dubbo过滤器jaeger集成方案,简略版(之前需要2019年写的,如果问题,如果好的想法,或者项目需要,可以沟通,邮箱358313250@qq.com,欢迎沟通)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值