授课语音

链路追踪的基本概念

一、链路追踪概述

链路追踪(Distributed Tracing)是一种用于跟踪跨多个微服务或分布式系统组件的请求流转的技术。它允许开发和运维人员清楚地看到请求在系统中的传播路径,帮助定位性能瓶颈、错误和延迟等问题,特别是在微服务架构中。

在微服务架构中,一个请求可能会跨越多个服务,每个服务可能在不同的机器上执行,而传统的日志记录方式无法提供跨服务的统一视图。链路追踪通过唯一的 Trace ID 和 Span ID 来标识和追踪请求,使得开发人员能够清晰地看到请求的生命周期,分析性能、排查故障。

二、链路追踪的核心概念

  • Trace:一个 Trace 代表用户请求的整个生命周期,可能会跨多个服务和操作。一个 Trace 包含多个 Span。
  • Span:一个 Span 表示一个具体的操作或服务调用,它记录了操作的开始时间、结束时间以及执行时的相关信息。Span 具有唯一的 ID 和可选的父 Span ID,构成了整个 Trace 的层级结构。
  • Trace ID:唯一标识一次请求生命周期的 ID。Trace ID 会在整个分布式请求链中传递。
  • Span ID:唯一标识一个操作(或服务节点)的 ID。每个 Span 都有一个唯一的 Span ID。
  • Parent Span ID:标识当前 Span 的父 Span ID,表示请求的父子关系,帮助构建请求流转的树形结构。
  • Annotations(注解):用于标识在 Span 执行期间发生的关键事件,例如服务调用的开始和结束等。

三、链路追踪的工作流程

  1. 请求到达服务:服务接收到请求时,会创建一个新的 Span,记录请求的基本信息,如请求的开始时间。
  2. 服务间调用:如果该服务需要调用其他服务,生成一个新的 Span,并将原有的 Trace ID 和 Span ID 一同传递给下游服务,以便形成一个完整的请求链。
  3. 请求完成:当请求完成时,Span 会记录结束时间,并将其上传到链路追踪系统中(如 Zipkin、Jaeger、OpenTelemetry)。

四、链路追踪的常用工具

  • OpenTelemetry:一个支持多语言的开源项目,用于统一分布式追踪、日志和指标的收集,支持与 Zipkin、Jaeger 等系统集成。
  • Zipkin:一个开源的分布式追踪系统,能够收集和可视化链路追踪数据。
  • Jaeger:由 Uber 开发的开源分布式追踪系统,支持多种存储后端,适用于大规模分布式环境。

五、链路追踪在 Go 中的实现

在 Go 中,我们可以使用 OpenTelemetry Go SDK 来实现链路追踪,OpenTelemetry 是目前最常用的分布式追踪标准,支持各种追踪系统(如 Zipkin、Jaeger)的集成。

六、Go代码案例(使用 OpenTelemetry)

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/trace/jaeger"
	"go.opentelemetry.io/otel/trace"
	"go.opentelemetry.io/otel/sdk/trace"
)

func main() {
	// 设置 Jaeger 导出器(将链路追踪数据发送到 Jaeger)
	exporter, err := jaeger.NewExporter(jaeger.WithCollectorEndpoint("http://localhost:5775"))
	if err != nil {
		log.Fatalf("failed to initialize Jaeger exporter: %v", err)
	}

	// 创建 TraceProvider,并注册到 OpenTelemetry
	tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
	otel.SetTracerProvider(tp)

	// 创建一个 Tracer(追踪器),用于生成 Span
	tracer := otel.Tracer("example-tracer")

	// 创建一个新的 Trace(主请求),生成 Trace ID 和 Span ID
	ctx, span := tracer.Start(context.Background(), "mainRequest")
	defer span.End() // 确保 Span 在函数结束时被关闭

	// 模拟服务 A 的处理
	simulateServiceA(ctx)

	// 模拟服务 B 的处理
	simulateServiceB(ctx)

	// 暂停一会儿,模拟程序执行
	time.Sleep(2 * time.Second)

	// 程序结束,所有追踪数据会被发送到 Jaeger
	fmt.Println("Trace data has been sent to Jaeger.")
}

// 模拟服务 A 的执行
func simulateServiceA(ctx context.Context) {
	// 在服务 A 中创建一个 Span,模拟服务 A 的操作
	tracer := otel.Tracer("example-tracer")
	_, span := tracer.Start(ctx, "serviceA")
	defer span.End()

	// 模拟处理过程
	fmt.Println("Service A processing...")
	time.Sleep(1 * time.Second)
}

// 模拟服务 B 的执行
func simulateServiceB(ctx context.Context) {
	// 在服务 B 中创建一个 Span,模拟服务 B 的操作
	tracer := otel.Tracer("example-tracer")
	_, span := tracer.Start(ctx, "serviceB")
	defer span.End()

	// 模拟处理过程
	fmt.Println("Service B processing...")
	time.Sleep(1 * time.Second)
}

七、代码解释

  1. Jaeger 导出器

    • 使用 jaeger.NewExporter 创建一个 Jaeger 导出器,将链路追踪数据发送到 Jaeger 服务。
    • 配置 jaeger.WithCollectorEndpoint 来指定 Jaeger 收集器的地址。
  2. TracerProvider 和 Tracer

    • trace.NewTracerProvider 创建一个新的 Trace Provider,并设置使用批量处理(WithBatcher)的方式来导出追踪数据。
    • otel.SetTracerProvider 注册 Trace Provider,使得 OpenTelemetry 能够使用它进行追踪操作。
  3. 创建 Trace 和 Span

    • tracer.Start(context.Background(), "mainRequest") 启动一个新的 Trace,它表示一个请求的生命周期,并生成一个 Trace ID。
    • defer span.End() 确保在函数执行完毕后关闭 Span,表示请求的结束。
  4. 服务 A 和服务 B

    • 在模拟的服务 A 和服务 B 中,分别创建了新的 Span(tracer.Start(ctx, "serviceA")tracer.Start(ctx, "serviceB"))。
    • 通过 time.Sleep 模拟服务的处理时间。
  5. 发送数据到 Jaeger

    • 程序执行完毕后,所有的链路追踪数据会自动被发送到 Jaeger 或配置的链路追踪系统。

八、如何查看链路追踪结果

  1. 启动 Jaeger:确保 Jaeger 服务正在运行并监听指定端口(如 5775)。
  2. 访问 Jaeger UI:通过浏览器访问 Jaeger UI(默认地址 http://localhost:5775),可以查询和可视化链路追踪数据。
    • 在 Jaeger UI 中输入 Trace ID,查看请求的详细链路图,分析请求的生命周期和服务间的调用关系。

九、总结

  • 链路追踪的核心概念:通过 Trace 和 Span 来跟踪一个请求的生命周期,帮助开发人员理解请求在分布式系统中的流转路径。
  • OpenTelemetry:是支持多种分布式追踪后端(如 Jaeger、Zipkin)的标准框架,可以帮助轻松实现链路追踪。
  • 服务间的依赖分析和性能瓶颈定位:链路追踪能够帮助开发人员快速定位服务间的依赖关系、性能瓶颈以及故障源。

通过 Go 语言结合 OpenTelemetry 和 Jaeger,开发人员可以方便地实现链路追踪,提高系统的可观测性,帮助快速排查问题和优化性能。

去1:1私密咨询

系列课程: