Introduction to Distributed Tracing with Jaeger and Ope

发布于:2023-09-27 ⋅ 阅读:(128) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

1.简介

分布式跟踪(Distributed tracing)是指应用程序组件之间的交互过程的可视化表示,用于帮助开发人员理解系统行为并进行故障排查、性能调优、可靠性分析等。该技术通常被用来监测微服务、云原生应用程序和基于容器的架构。Jaeger是一个开源分布式跟踪系统,由uber开源并贡献给云原生计算联盟(CNCF)。

本文主要介绍OpenTracing API和Jaeger的功能,为读者提供一个完整的使用指南。在阅读本文之前,需要读者对OpenTracing标准协议有基本的了解。本文假设读者已经安装了Java、Maven、Docker及Kubernetes相关环境,并熟悉相关概念,如进程间通信、分布式系统和微服务架构。另外,本文不涉及实际案例研究,而是通过一些示例代码演示了Jaeger的基本用法。

本文将从以下几个方面进行介绍:

  1. OpenTracing介绍。
  2. Jaeger系统架构。
  3. 使用Jaeger构建一个简单的分布式追踪系统。
  4. Jaeger UI的使用方法。
  5. Jaeger客户端库的选择。
  6. Jaeger部署到Kubernetes集群上的方法。
  7. 总结。

2.OpenTracing介绍

OpenTracing 是 CNCF 提供的一套开放标准,它定义了一种标准化的接口,可以用来向已有的应用中添加分布式跟踪能力。该规范致力于成为应用程序间的通用跟踪解决方案,使得各种分布式环境下的监控、日志和跟踪工具可以相互集成。

2.1 分布式跟踪的意义

当用户访问一个网站时,不仅需要根据网站的URL定位服务器,还要知道每个请求的响应时间、返回码、错误信息等其他详细信息。对于复杂的分布式系统来说,这种能力是至关重要的。比如说,网站首页的加载时间过长,导致用户点击刷新按钮等待反应的场景;或者是用户由于网络问题无法正常登录某个功能模块,这时候管理员只能够通过查看日志文件来发现问题所在。在这些情况下,如果能够通过跟踪系统捕获每个请求的全过程信息,就能够准确诊断出问题的原因,改善系统的可用性和性能,提升客户体验。

2.2 OpenTracing的作用

OpenTracing 作为分布式跟踪的标准规范,旨在定义一种统一的调用上下文格式,并通过定义统一的抽象API来向各种语言、框架和中间件提供分布式跟踪的能力。这样就可以实现跨不同框架和编程模型的应用之间的分布式跟踪,并让开发者更方便地接入和集成分布式跟踪功能。其主要作用如下所示:

  1. 为监控和度量数据提供统一的数据格式,屏蔽底层数据源的差异。

  2. 为开发者提供一个统一的API,通过API,可以轻松地创建分布式跟踪的上下文、记录事件、发送span数据。

  3. 提供统一的抽象,可以在不同的语言、框架和组件之间复用相同的代码,提高透明度。

  4. 支持多种可插拔的后端存储系统,包括内存、基于文件的存储、数据库、消息队列和其他分布式跟踪系统。

  5. 通过插件机制,可以支持不同的传输方式,如RESTful API、gRPC和Kafka等。

  6. 对Tracing数据进行采样,降低收集量和带宽占用,提高性能。

  7. 支持分布式追踪中的跨进程跟踪。

2.3 调用链路追踪

调用链路追踪(trace)描述的是一个事务过程中所有的相关活动。其一般包括四个主要元素:

  1. Trace ID: 一串唯一标识符,用来唯一标记一次事务。

  2. Span ID: 一串唯一标识符,用来唯一标记一次操作。

  3. Parent span: 表示当前Span的父级Span。

  4. Child span: 表示当前Span的子级Span。

调用链路追踪的一个最典型的例子就是支付宝支付流程。在这个流程中,需要经历多个子系统的协同才能完成整个交易。每个子系统都可以独立完成自己的工作,但又需要互相协作,因此会产生很多依赖关系。通过调用链路追踪,就可以清晰地展示整个支付流程中的各个子系统和调用关系。

2.4 OpenTracing API

OpenTracing提供的API包括三个部分:

  • Tracer接口:Tracer接口用于生成新的spans,并把它们绑定到调用栈上。
  • Span接口:Span接口用于描述一个操作的范围,比如一个远程过程调用,HTTP 请求,数据库查询等。它具有开始时间、结束时间、标签键值对、上下文信息等属性。
  • ScopeManager接口:ScopeManager接口管理Span的生命周期,并控制Span的进入和退出。

下图展示了OpenTracing API的组成:

2.5 OpenTracing术语表

OpenTracing提供了一份术语表来帮助理解OpenTracing的各种概念。以下是术语表的摘要:

名称 描述
Tracer 负责生成和 propagating spans 的实体。
Context 传递 trace 和 span 的 carrier 对象。
Propagator 将 span context 从一个 process 传播到另一个 process 中。
Span 代表一个跨度(operation),记录了事件的时间顺序,并且可以嵌套子 Span 。
Scope Manager 管理 scope,即决定何时开始或结束一个 span。
Reference 引用是指向其它 spans 的指针。

3.Jaeger系统架构

Jaeger是一个开源分布式跟踪系统,由uber开源并贡献给云原生计算联盟(CNCF)。Jaeger包含三个主要组件:

  1. Jaeger客户端库:由语言独立的库组成,例如 Java、Go、Node.js 等。它们负责生成、收集、处理和报告 spans。

  2. 数据存储组件:接收、存储和查询 spans。Jaeger 提供了一个独立的选项,可以让您部署自己的自定义数据存储。目前支持 Cassandra、ElasticSearch、Kafka、MongoDB、MySQL 或 PostgreSQL 等。

  3. 查询服务:查询服务是一个独立的组件,用来检索和过滤 spans。它可以使用 UI 来呈现 traces,或者提供其他查询语言的 API。

下图展示了Jaeger的系统架构:

4.使用Jaeger构建一个简单的分布式追踪系统

在本节中,我们将用 Java 编写一个简单的分布式追踪系统,来演示如何使用 Jaeger 组件。

4.1 创建 Maven 项目

首先,创建一个名为 simple-tracing 的 Maven 项目。为简单起见,我们将运行一个 Spring Boot 应用,该应用包含一个简单的 REST 服务,它接收 GET 请求,返回固定字符串。

pom.xml 文件如下所示:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>simple-tracing</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 添加 opentracing-util 依赖来生成Spans-->
        <dependency>
            <groupId>io.opentracing</groupId>
            <artifactId>opentracing-util</artifactId>
            <version>0.33.0</version>
        </dependency>

        <!-- 添加 jaeger-core 依赖来连接 Jaeger client 和 server -->
        <dependency>
            <groupId>io.jaegertracing</groupId>
            <artifactId>jaeger-core</artifactId>
            <version>1.6.0</version>
        </dependency>

    </dependencies>

</project>

application.properties 文件中,配置数据存储器和端口号:

server.port=9000
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=

# 使用本地内存作为数据存储器
spring.profiles.active=dev
# 使用 Elasticsearch 作为数据存储器
#spring.profiles.active=prod-es
#spring.data.elasticsearch.cluster-nodes=localhost:9200
# 配置 Elasticsearch Index
#spring.data.elasticsearch.properties.index.number_of_shards=1
#spring.data.elasticsearch.properties.index.number_of_replicas=0
# 指定 Jaeger Client 的地址
#spring.jaeger.agent-host-port=localhost:6831

Spring Boot 在启动时,它会根据配置文件激活相应的 profile。在 dev 模式下,它默认使用 H2 内存数据库做为数据存储器,而在 prod-es 模式下,它会使用 Elasticsearch 作为数据存储器。

为了创建 spans,我们需要引入 opentracing-util 依赖,该依赖包中包含一个 GlobalTracer,我们可以通过它来获取全局 tracer 实例。

4.2 编写业务逻辑代码

编写 GreetingController 来处理 HTTP 请求,它有一个 greeting() 方法来生成问候语。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import io.opentracing.*;

@RestController
public class GreetingController {

    private static final Logger logger = LoggerFactory.getLogger(GreetingController.class);
    private static final String SERVICE_NAME = "greeting";
    // 获取全局tracer实例
    private static Tracer tracer = GlobalTracer.get();

    @GetMapping("/greeting")
    public String greeting() {

        try (Scope ignored = tracer.buildSpan("greeting").asChildOf(tracer.activeSpan().context()).startActive(true)) {
            return "Hello World!";
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }

    }

}

这里,我们使用 try-with-resources 语法自动关闭 spans,使用 scopeManager 来管理 scopes,并获取全局 tracer 实例。我们构建了一个名为 "greeting" 的 span,并把它设置为它的父级 span,然后将其开启。

4.3 初始化 Jaeger 客户端

编写 SimpleTracingApplication 来初始化 Jaeger client:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.uber.jaeger.Configuration;
import com.uber.jaeger.Configuration.ReporterConfiguration;
import com.uber.jaeger.Configuration.SamplerConfiguration;
import com.uber.jaeger.samplers.ConstSampler;
import com.uber.jaeger.senders.UdpSender;

@SpringBootApplication
public class SimpleTracingApplication implements CommandLineRunner {

    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(SimpleTracingApplication.class, args);
    }

    @Autowired
    Configuration configuration;

    @Override
    public void run(String... args) throws Exception {
        SamplerConfiguration samplerConfig =
                new SamplerConfiguration().withType(ConstSampler.TYPE).withParam(1);

        ReporterConfiguration reporterConfig =
                new ReporterConfiguration().withLogSpans(true).withFlushIntervalMs(1000).
                        withMaxQueueSize(1000).withSender(new UdpSender());

        configuration = new Configuration(SERVICE_NAME,
                samplerConfig, reporterConfig);

    }
}

这里,我们通过 Spring 注入的方式初始化 Jaeger client。我们设置了一个定制化的 Sampler 配置,并指定了使用的 Sender,该 Sender 会将 spans 发送到指定的地址。然后,我们通过 configuration 变量保存配置。

4.4 启用追踪功能

application.properties 文件中,开启追踪功能:

logging.level.root=TRACE
spring.sleuth.enabled=true
spring.zipkin.enabled=false

logging.level.root 属性可以调整日志级别,此处我们调整为 TRACE 以便查看 spans 相关的信息。spring.sleuth.enabled 属性开启 Sleuth 的 tracing 功能,spring.zipkin.enabled 属性关闭 Zipkin 的 tracing 功能,避免冲突。

4.5 执行测试

编译项目,执行以下命令运行 Spring Boot 应用:

mvn clean package
java -jar target/simple-tracing-1.0-SNAPSHOT.jar

打开浏览器访问 http://localhost:9000/greeting,然后查看 logs 中的 spans 信息:

2021-02-09 16:19:51.181 TRACE 36330 --- [nio-9000-exec-1] o.s.c.support.DefaultLifecycleProcessor  : Failed to start bean'metricBeans'; nested exception is java.lang.UnsupportedOperationException
2021-02-09 16:19:51.702 DEBUG 36330 --- [nio-9000-exec-1] c.u.j.Configuration                   : Initializing a ConstSampler with parameter 1
2021-02-09 16:19:51.704 INFO 36330 --- [nio-9000-exec-1] i.o.p.s.b.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4c8a0d46: startup date [Wed Feb 09 16:19:51 CST 2021]; root of context hierarchy
2021-02-09 16:19:52.313 DEBUG 36330 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : Initialization of Spring DispatcherServlet complete in 610 ms
2021-02-09 16:19:52.565 DEBUG 36330 --- [nio-9000-exec-1].w.s.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting]}" onto public java.lang.String com.example.simpletracing.controller.GreetingController.greeting()
2021-02-09 16:19:52.579 DEBUG 36330 --- [nio-9000-exec-1] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ResponseBody methodExceptionHandlerMethodResolver: public java.lang.Object org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.NativeWebRequest)
2021-02-09 16:19:52.581 DEBUG 36330 --- [nio-9000-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "null /greeting null" onto public java.lang.String com.example.simpletracing.controller.GreetingController.greeting()
2021-02-09 16:19:52.677 DEBUG 36330 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 81 ms
2021-02-09 16:19:53.239 DEBUG 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Created POST request for "http://localhost:9000/greeting"
2021-02-09 16:19:53.319 TRACE 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Getting http://localhost:9000/greeting
2021-02-09 16:19:53.329 DEBUG 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Closing HttpClient
2021-02-09 16:19:53.359 DEBUG 36330 --- [nio-9000-exec-1] ration$$EnhancerBySpringCGLIB$$bbfaedca : Creating instance of GreetingController
2021-02-09 16:19:53.424 DEBUG 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Created GET request for "http://localhost:9000/"
2021-02-09 16:19:53.434 TRACE 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Getting http://localhost:9000/
2021-02-09 16:19:53.444 DEBUG 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Closing HttpClient
2021-02-09 16:19:53.445 TRACE 36330 --- [nio-9000-exec-1] o.s.web.client.RestTemplate              : Response 200 OK
2021-02-09 16:19:53.504 DEBUG 36330 --- [nio-9000-exec-1].a.ControllerMethodInvocableHandlerMethod : Handling "GET" dispatch for [/]
2021-02-09 16:19:53.504 DEBUG 36330 --- [nio-9000-exec-1].s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{rel=[self], href=[http://localhost:9000/]}] using [org.springframework.hateoas.MediaTypes$HalJson]
2021-02-09 16:19:53.534 DEBUG 36330 --- [nio-9000-exec-1].t.e.SpringBootErrorController       : Using resolved error view in response entity.
2021-02-09 16:19:53.535 DEBUG 36330 --- [nio-9000-exec-1] f.s.WebFluxResponseStatusExceptionHandler : No match found for status code 404
2021-02-09 16:19:53.564 DEBUG 36330 --- [nio-9000-exec-1] o.s.w.s.mvc.method.RequestMappingInfoHandlerMapping : URI=/greeting
2021-02-09 16:19:53.574 DEBUG 36330 --- [nio-9000-exec-1] o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json' given [*/*] and supported [application/json, application/*+json, application/json_seq]
2021-02-09 16:19:53.584 DEBUG 36330 --- [nio-9000-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{message=Hello World!}]
2021-02-09 16:19:53.584 TRACE 36330 --- [nio-9000-exec-1] o.s.web.server.DefaultWebServer          : Encoding [TextOutputMessage, CompositeByteBuf(ridx: 0, widx: 15/{length=15}, cap: 15)] using [org.springframework.http.codec.json.Jackson2CodecSupport]
2021-02-09 16:19:53.595 DEBUG 36330 --- [nio-9000-exec-1] o.s.w.s.adapter.HttpWebHandlerAdapter    : [2cd8e6c1] Resolved [java.lang.String]
2021-02-09 16:19:53.595 DEBUG 36330 --- [nio-9000-exec-1] o.s.w.s.adapter.HttpWebHandlerAdapter    : [2cd8e6c1] Writing [b'{"message":"Hello World!"}']
2021-02-09 16:19:53.624 DEBUG 36330 --- [ctor-http-nio-3] reactor.netty.transport.TransportConfig   : Connect timeout configured so applying it globally: 5000 MILLISECONDS
2021-02-09 16:19:53.644 DEBUG 36330 --- [ctor-http-nio-3] r.n.resources.PooledConnectionProvider   : [id: 0x3c0e14b5] Created a pooled channel, active connections count: 1
2021-02-09 16:19:53.654 DEBUG 36330 --- [ctor-http-nio-3] r.n.resources.PooledConnectionProvider   : [id: 0x3c0e14b5, L:/127.0.0.1:61142! R:localhost/127.0.0.1:6831] Registering pool release on close event for channel
2021-02-09 16:19:53.674 DEBUG 36330 --- [ctor-http-nio-3] r.n.http.client.HttpClientOperations     : [id: 0x3c0e14b5, L:/127.0.0.1:61142! R:localhost/127.0.0.1:6831] Received response (auto-read:false) : [DefaultHttpResponse(decodeResult: success, version: HTTP/1.1)
HTTP/1.1 200 OK
connection: keep-alive
transfer-encoding: chunked
content-type: application/json;charset=UTF-8

4b
{"message":"Hello World!"}
0

]
2021-02-09 16:19:53.685 DEBUG 36330 --- [ctor-http-nio-3] r.n.resources.PooledConnectionProvider   : [id: 0x3c0e14b5, L:/127.0.0.1:61142! R:localhost/127.0.0.1:6831] Channel cleaned up, now 0 active connections and 1 inactive connections total
2021-02-09 16:19:53.685 DEBUG 36330 --- [ctor-http-nio-3] r.n.http.client.HttpClientOperations     : [id: 0x3c0e14b5, L:/127.0.0.1:61142! R:localhost/127.0.0.1:6831] Received last chunk

可以看到,Jaeger client 已经创建了一个名为 "greeting" 的 span,并将其设置为它的父级 span,然后将其开启。

本文含有隐藏内容,请 开通VIP 后查看