当前位置: 首页 > news >正文

go链路追踪

trace 的玩法

下面是基于 otel 结合 jaeger 实现的基于 grpc 分布式的链路追踪

了解什么是链路追踪?

定义

简单说,链路追踪是指在分布式系统中,将一次请求的所有调用过程进行记录和展示,以便于开发人员快速定位和解决问题。

作用

  • 定位问题:通过记录和展示一次请求的所有调用过程,开发人员可以快速定位问题所在,减少定位问题的时间。
  • 优化性能:通过分析一次请求的所有调用过程,开发人员可以了解系统的性能瓶颈,从而优化系统的性能。
  • 监控系统:通过记录和展示一次请求的所有调用过程,开发人员可以了解系统的运行状态,及时发现系统的问题。

实现原理

链路追踪的实现原理主要是通过在分布式系统中添加一个中间件,该中间件负责记录和展示一次请求的所有调用过程。在一次请求中,每个服务都需要添加一个中间件,该中间件负责记录和展示该服务的调用过程。

工作流程

在链路追踪的业务架构上讲,他的工作流程是:

  1. 客户端发送请求到服务端。
  2. 服务端收到请求后,先添加一个中间件,该中间件负责记录和展示该服务的调用过程。
  3. 服务端调用其他服务时,也需要添加一个中间件,该中间件负责记录和展示该服务的调用过程。
  4. 服务端返回响应给客户端时,中间件负责记录和展示该服务的调用过程。
  5. 客户端收到响应后,中间件负责记录和展示该服务的调用过程。
  6. 链路追踪的中间件会将一次请求的所有调用过程记录下来,开发人员可以在 jaeger 中查看这些记录。

在链路追踪的实现架构上讲,他的工作流程是:
链路追踪系统(以 Jaeger 为例)的工作流程依赖于多个核心组件的协作,从数据生成到最终可视化,形成完整的追踪链路。以下是各组件的角色及协作流程:

核心组件

链路追踪系统通常包含 4 个核心组件,各组件职责明确且紧密协作:

组件作用
客户端 SDK嵌入到业务应用中,负责生成、采集追踪数据(如 span、标签、日志)
Agent本地代理服务,轻量级组件,接收 SDK 发送的追踪数据并转发给 Collector
Collector收集器,接收 Agent 或 SDK 直接发送的数据,进行验证、清洗、存储
Storage存储组件,持久化保存追踪数据(如 Elasticsearch、Cassandra、Badger)
Query查询服务,提供 API 接口,支持从 Storage 中查询追踪数据并展示给 UI
UI可视化界面,供用户查询、分析追踪链路(如调用关系、耗时、错误信息)

完整工作流程

  1. 追踪数据生成(客户端 SDK)

    • 业务应用通过集成链路追踪 SDK(如 Jaeger SDK),在代码中自动或手动生成追踪数据:
      • Span:表示一个独立的工作单元(如一次 RPC 调用、数据库操作),包含唯一 ID、父 Span ID(用于关联调用链路)、开始/结束时间、标签(如服务名、方法名)、日志(如错误信息)等。
      • Trace:由多个相关联的 Span 组成,代表一个完整的分布式请求链路(如用户请求从网关到多个微服务的全过程)。
    • SDK 会通过上下文(Context)传递追踪信息(如 Trace ID、Span ID),确保跨服务调用时链路的连贯性。
  2. 数据发送(SDK → Agent)

    • SDK 生成追踪数据后,通过 UDP 或 HTTP 协议发送到本地的 Agent(通常与应用部署在同一台机器)。
    • 选择 Agent 的原因:
      • 减少 SDK 与 Collector 的网络交互开销(Agent 本地通信更快)。
      • 批量处理数据,降低 Collector 的压力。
      • 隔离应用与 Collector 的依赖(Agent 负责重试、缓冲,避免 Collector 故障影响应用)。
  3. 数据汇聚(Agent → Collector)

    • Agent 接收 SDK 的数据后,进行简单处理(如批量打包),再转发给 Collector(通常通过 TCP 协议)。
    • 部分场景下,SDK 也可跳过 Agent 直接向 Collector 发送数据(如 Agent 未部署时)。
  4. 数据处理与存储(Collector → Storage)

    • Collector 接收数据后,执行验证(格式检查)、清洗(过滤无效数据)、转换(统一格式)等操作。
    • 处理完成后,将数据持久化到 Storage(如 Elasticsearch 适合分布式存储和高效查询)。
    • 部分系统支持数据采样(如只保留 10% 的追踪数据),避免存储压力过大。
  5. 查询与可视化(Query → UI)

    • 用户通过 UI(如 Jaeger UI)输入查询条件(如 Trace ID、服务名、时间范围)。
    • UI 向 Query 服务发送请求,Query 从 Storage 中查询符合条件的追踪数据。
    • Query 将数据解析为结构化的链路信息(如调用树、各节点耗时),返回给 UI 展示。
    • 最终用户可在 UI 中查看完整链路:服务调用关系、每个步骤的耗时、错误节点等,用于排查性能瓶颈或故障。

组件协作示例(一次用户请求)

  1. 用户请求到达网关服务,网关 SDK 生成初始 Span(根 Span),并将 Trace ID 传递给下游服务 A。
  2. 服务 A 接收请求,生成子 Span,调用服务 B 时携带 Trace ID 和父 Span ID。
  3. 服务 B 生成子 Span,完成处理后返回结果,SDK 将 Span 数据发送到本地 Agent。
  4. Agent 汇总服务 A、B、网关的 Span 数据,转发给 Collector。
  5. Collector 处理后存储到 Elasticsearch。
  6. 用户在 Jaeger UI 中查询该 Trace ID,Query 从 Elasticsearch 读取数据,UI 展示完整链路(网关 → 服务 A → 服务 B)及各步骤耗时。

启动 jaeger

docker run --rm --name jaeger
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411
-p 6831:6831/udp
-p 6832:6832/udp
-p 5778:5778
-p 16686:16686
-p 4317:4317
-p 4318:4318
-p 14250:14250
-p 14268:14268
-p 14269:14269
-p 9411:9411
jaegertracing/all-in-one:1.55

PortProtocolComponentFunction
6831UDPagentaccept jaeger.thrift over Thrift-compact protocol (used by most SDKs)
6832UDPagentaccept jaeger.thrift over Thrift-binary protocol (used by Node.js SDK)
5775UDPagent(deprecated) accept zipkin.thrift over compact Thrift protocol (used by legacy clients only)
5778HTTPagentserve configs (sampling, etc.)
16686HTTPqueryserve frontend
4317HTTPcollectoraccept OpenTelemetry Protocol (OTLP) over gRPC
4318HTTPcollectoraccept OpenTelemetry Protocol (OTLP) over HTTP
14268HTTPcollectoraccept jaeger.thrift directly from clients
14250HTTPcollectoraccept model.proto
9411HTTPcollectorZipkin compatible endpoint (optional)

容器启动后,使用浏览器打开 http://localhost:16686 即可访问 Jaeger UI。

什么是 open telemetry?

open telemetry 是一个开源的 observability 框架,用于收集、处理、导出分布式系统的遥测数据(如指标、日志、跟踪)。它提供了一个统一的 API,支持多种语言和多种后端存储。
简单的说,他就是一个标准,上面的jaeger实现了这个标准。
open telemetry 定义了数据模型(如 Span、Trace、Metric)和 API,不同的实现(如 Jaeger、Prometheus、Zipkin)根据这个标准进行数据采集、处理和导出。

otel + jaeger + grpc

初始化 tracer

package traceimport ("context""go.opentelemetry.io/otel""go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc""go.opentelemetry.io/otel/propagation""go.opentelemetry.io/otel/sdk/resource"sdktrace "go.opentelemetry.io/otel/sdk/trace"semconv "go.opentelemetry.io/otel/semconv/v1.31.0""time"
)const (serviceName       = "gRPC-Jaeger-Demo"// 应用集群名jaegerRPCEndpoint = "127.0.0.1:4317" // Jaeger RPC端口
)// 定义每个服务上的Tracer:单独记录每个服务链路,便于追踪数据的区分
var (TracerA = otel.Tracer("service-a")TracerB = otel.Tracer("service-b")TracerC = otel.Tracer("service-c")
)// newJaegerTraceProvider 创建一个 Jaeger Trace Provider
func newJaegerTraceProvider(ctx context.Context) (*sdktrace.TracerProvider, error) {// 创建一个使用 HTTP 协议连接本机Jaeger的 Exporterexp, err := otlptracegrpc.New(ctx,otlptracegrpc.WithEndpoint(jaegerRPCEndpoint),otlptracegrpc.WithInsecure())if err != nil {return nil, err}res, err := resource.New(ctx, resource.WithAttributes(semconv.ServiceName(serviceName)))if err != nil {return nil, err}traceProvider := sdktrace.NewTracerProvider(sdktrace.WithResource(res),sdktrace.WithSampler(sdktrace.AlwaysSample()), // 采样sdktrace.WithBatcher(exp, sdktrace.WithBatchTimeout(time.Second)),)return traceProvider, nil
}// InitTracer 初始化 Tracer
func InitTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {tp, err := newJaegerTraceProvider(ctx)if err != nil {return nil, err}otel.SetTracerProvider(tp)otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}),)return tp, nil
}

Server 端

  1. 初始化 tracer
// 初始化 Tracerctx := context.Background()tp, err := traceCli.InitTracer(ctx)if err != nil {log.Fatal(err)}defer func() {if err := tp.Shutdown(context.Background()); err != nil {log.Printf("Error shutting down tracer provider: %v", err)}}()
  1. 注册 tracer 到 grpcServer 中
// 创建grpc服务器grpcServer := grpc.NewServer(grpc.StatsHandler(otelgrpc.NewServerHandler()), // 设置 StatsHandler)
  1. 对服务的方法中手动添加span
    1. 在方法前面,创建span,用于继承上下文
    2. 在方法调用前,创建span,用于记录方法调用
    3. 记得结束span

Client 端

  1. 初始化 tracer
// 初始化 Tracerctx := context.Background()tp, err := traceCli.InitTracer(ctx)if err != nil {log.Fatal(err)}defer func() {if err := tp.Shutdown(context.Background()); err != nil {log.Printf("Error shutting down tracer provider: %v", err)}}()
  1. 注册 tracer 到 grpcClient 中
	// 连接 A 服务conn1, err := grpc.NewClient(addr1, grpc.WithInsecure(), grpc.WithStatsHandler(otelgrpc.NewClientHandler()))if err != nil {panic(err)}
  1. 生成上下文,比如:
func CreateContext() context.Context {// 生成上下文: 包含必要的 client_id、user_id 等md := metadata.Pairs("timestamp", time.Now().Format(time.StampNano),"client-id", "client-lion","user-id", "yym",)ctx := metadata.NewOutgoingContext(context.Background(), md)return ctx
}

网关服务

中间件

用于生成根span,eg:

package midimport ("github.com/gin-gonic/gin""go.opentelemetry.io/otel/attribute""go.opentelemetry.io/otel/codes"semconv "go.opentelemetry.io/otel/semconv/v1.17.0""go.opentelemetry.io/otel/trace""net/http"traceCli "simple-grpc/trace"
)// TracingMiddleware 为每个请求创建根Span,作为链路起点
func TracingMiddleware() gin.HandlerFunc {return func(c *gin.Context) {// 从请求中提取可能存在的追踪上下文(如客户端传递的)// 没有则创建全新的根Spanctx, span := traceCli.TracerGateway.Start(c.Request.Context(),"Gateway "+c.Request.Method+" "+c.Request.URL.Path, // Span名称:包含HTTP方法和路径trace.WithAttributes(semconv.HTTPMethodKey.String(c.Request.Method),         // 标准HTTP方法属性semconv.HTTPTargetKey.String(c.Request.URL.Path),       // 请求路径semconv.HTTPUserAgentKey.String(c.Request.UserAgent()), // 用户代理attribute.String("client.ip", c.ClientIP()),            // 客户端IP),)defer span.End() // 确保请求结束时关闭Span// 将包含根Span的上下文注入到Gin上下文,供后续处理函数使用c.Set("traceContext", ctx)// 处理请求c.Next()// 记录HTTP响应状态码span.SetAttributes(semconv.HTTPStatusCodeKey.Int(c.Writer.Status()))// 如果请求出错,标记Span状态if c.Writer.Status() >= http.StatusInternalServerError {span.SetStatus(codes.Error, "请求处理失败")}}
}

网关集成

  1. 先初始化 tracer
  2. use 中间件
  3. handler函数中,从 中间件传递的context 中获取链路追踪的上下文,用于调用 rpc 服务

结果图

在这里插入图片描述

http://www.lryc.cn/news/626133.html

相关文章:

  • 微算法科技(NASDAQ: MLGO)研究利用PBFT中的动态视图变换机制,实现区块链系统高效运转
  • 不同语言的并发模型对比:Go、Java与Python
  • Go高效复用对象:sync.Pool详解
  • 机器学习中的「损失函数」:模型优化的核心标尺
  • 决策树算法详解
  • 【完整源码+数据集+部署教程】鳄梨表面缺陷检测图像分割系统源码和数据集:改进yolo11-MLCA
  • QT聊天项目DAY19
  • 广东省省考备考(第八十一天8.19)——资料分析、数量(强化训练)
  • 第5.5节:awk算术运算
  • 基于深度学习的森林火灾图像识别实战
  • 【撸靶笔记】第七关:GET - Dump into outfile - String
  • 浙江电信IPTV天邑TY1613_高安版_晶晨S905L3SB_安卓9_原厂固件自改_线刷包
  • Linux中Docker k8s介绍以及应用
  • windows电脑对于dell(戴尔)台式的安装,与创建索引盘,系统迁移到新硬盘
  • 微信小程序连接到阿里云物联网平台
  • 高等数学 8.6 空间曲线及其方程
  • 添加右键菜单项以管理员权限打开 CMD
  • DNS有关知识(根域名服务器、顶级域名服务器、权威域名服务器)
  • 【C语言16天强化训练】从基础入门到进阶:Day 3
  • Vue 2 项目中快速集成 Jest 单元测试(超详细教程)
  • 【矢量数据】1:250w中国地质图地断层数据/岩性shp数据
  • EPM240T100I5N Altera FPGA MAX II CPLD
  • 无人机/航测/三维建模领域常见的“航线规划或建模方式
  • Everything 搜索工具下载安装使用教程(附安装包)Everything
  • 在 Python 中操作 Excel 文件的高效方案 —— Aspose.Cells for Python
  • mycat分库分表实验
  • [激光原理与应用-302]:光学设计 - 光学设计的流程、过程、方法、工具
  • mlir replace
  • C#传参调用外部exe
  • 线段树结合矩阵乘法优化动态规划