本文目录导读:

构建一个全链路日志追踪系统,核心目标是解决在微服务架构或分布式系统中,一个请求经过多个服务时,如何将分散在各个服务中的日志串联起来,形成一个完整的“请求链路”。
下面是一个系统化的构建指南,从核心概念到具体实现都涵盖。
核心概念:Trace 和 Span
这是理解整个系统的基石。
- Trace(追踪): 代表一个完整的请求链路,从入口(如用户请求)到最终响应的整个过程,一个 Trace 由一个全局唯一的
TraceID标识。 - Span(跨度): 代表 Trace 中的一个具体工作单元,比如调用一个数据库、调用另一个微服务、执行一段业务逻辑,每个 Span 也有自己的
SpanID,并记录其父 Span 的ParentSpanID,从而形成层级关系,Span 包含开始时间、结束时间、状态、标签等信息。
简单比喻:
Trace 是一场漫长的旅行(从北京到上海),Span 是旅行中的各个步骤(去机场、坐飞机、打车去酒店)。TraceID 是这次旅行的订单号,SpanID 是每个步骤的收据。
构建系统的关键步骤
构建全链路追踪系统可以分为几个核心阶段,你可以根据自身的资源和技术栈选择不同的实现路径。
确定数据模型与标准
这是理论指导,决定了系统的通用性和扩展性,目前最主流的两个标准:
- OpenTelemetry (OTel): 当前业界的事实标准,强烈推荐。 它统一了数据采集、传输和处理的格式,提供了各种语言的 SDK 和 API,它支持 OpenTracing 和 OpenCensus 的合并协议。
- OpenTracing / OpenCensus: 早期标准,目前建议逐步迁移到 OpenTelemetry。
你的选择: 直接基于 OpenTelemetry 进行构建或选用其兼容的商业/开源方案。
组件构成
一个完整的全链路追踪系统通常包含以下四个核心组件:
- 数据采集器(Agent / SDK): 嵌入到你的微服务应用中。
- 功能: 自动或手动生成 TraceID、SpanID,记录 Span 的开始/结束时间、上下文信息、标签等。
- 实现: 使用 OpenTelemetry SDK(Java、Go、Python、Node.js 等)。
- 关键操作: 上下文传播,这是最核心的挑战,在一个服务收到请求时,需要从 HTTP 请求头、RPC 协议(如 gRPC metadata)或消息队列的头部中提取父 Span 的信息(TraceID, SpanID, Baggage),并将其注入到当前线程的上下文中,以便在当前服务内部的所有异步调用和子 Span 中传递,调用下一个服务时,需要将当前 Span 的上下文序列化到出站请求的头中(如
traceparent头)。
- 数据传输器(Collector):
- 功能: 接收从 Agent 发送来的 Span 数据,进行缓存、批处理、过滤、采样,然后发送到后端存储系统。
- 实现: OpenTelemetry Collector 是目前最主流的选择,它是一个独立运行的服务,可以配置多种接收器、处理器和导出器。
- 数据存储与分析后端(Backend):
- 功能: 接收、索引、存储 Span 数据,并提供强大的搜索引擎和可视化仪表盘。
- 常见选择:
- 开源方案(免费、可控,但运维成本较高):
- Jaeger: 专为分布式追踪设计,功能全面,性能优秀,使用 Elasticsearch、Cassandra 或 Badger 作为存储,社区活跃。
- Zipkin: 较老牌,成熟稳定,使用 MySQL、Elasticsearch 等存储。
- SigNoz: 较新的、基于 ClickHouse 的全栈开源 APM 工具,开箱即用,性能极好。
- 商业方案(付费、省心、功能更强大):
- Datadog APM: 非常易用,集成度极高。
- New Relic: 老牌 APM 提供商,功能强大。
- AWS X-Ray: 如果使用 AWS 环境,集成方便。
- Google Cloud Trace、Azure Monitor: 对应云厂商的服务。
- 开源方案(免费、可控,但运维成本较高):
- 可视化与告警前端(UI):
- 功能: 展示 Trace 的拓扑图、瀑布图、调用详情、错误定位、性能分析、慢请求追踪等。
- 实现: 上述 Jaeger、Zipkin、SigNoz 以及商业方案都自带 UI,你也可以基于 Grafana 集成(通过 Tempo 数据源)。
技术选型建议(含具体方案)
根据你的阶段和预算,给出三种典型方案:
开源方案(技术深度、高可控)
- 技术栈:
OpenTelemetry SDK+OpenTelemetry Collector+Jaeger+Elasticsearch - 优点: 完全开源免费,社区活跃,灵活可控。
- 缺点: 需要自行搭建和维护基础设施(ES、Collector、Jaeger),运维成本较高。
- 适用场景: 有一定运维能力的技术团队,或对成本敏感、需要深度定制的场景。
- 简单架构图:
应用A (OTel SDK) --> OpenTelemetry Collector --> Jaeger (Query & UI) --> Elasticsearch (存储) 应用B (OTel SDK) --> OpenTelemetry Collector --> (同上) 网关 (OTel SDK) --> OpenTelemetry Collector --> (同上)
全栈开源方案(易上手、快速搭建)
- 技术栈:
OpenTelemetry SDK+SigNoz - 优点: 开箱即用,SigNoz 集成了 Collector、存储(ClickHouse,性能极强)和 UI,部署非常简单(可用 Docker Compose 一键启动),UI 现代化,功能和商业 APM 非常接近。
- 缺点: 比 Jaeger 相对年轻,社区规模稍小。
- 适用场景: 想要快速搭建、体验全链路追踪,不希望维护复杂基础设施的团队。
商业方案(省心、强大、成本高)
- 技术栈:
Datadog APM 或 New Relic(Agent 自动注入) - 优点: 零运维,功能极其强大(自动发现、分布式拓扑、智能告警、异常检测、集成日志、监控),开箱即用,无需任何配置。
- 缺点: 费用较高,数据存储在供应商处,数据隐私和合规需评估。
- 适用场景: 预算充足,希望将精力集中在业务开发上,对性能分析和实时告警有高要求的团队。
核心实施细节(以 OpenTelemetry + Jaeger 为例)
-
应用接入(以 Java Spring Boot 为例):
- 添加依赖:
io.opentelemetry:opentelemetry-exporter-otlp和io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter。 - 配置
application.properties或环境变量:# 将 Span 发送到 OpenTelemetry Collector 的地址 otel.exporter.otlp.endpoint=http://127.0.0.1:4318 # 服务名称 otel.service.name=my-service # 采样率(生产环境建议设置为 0.1~1.0,或使用基于头部/概率的采样) sampler.type=parentbased_traceidratio sampler.argument=1.0
- 添加依赖:
-
上下文传播示例(HTTP): 当服务A调用服务B时,OpenTelemetry SDK 会自动在 HTTP 请求头中注入
traceparent和tracestate头,服务B的 SDK 会自动从请求头中提取这些信息,创建新的 Span 并正确关联,开发者通常无需手动处理,除非使用自定义协议。- 跨线程/异步调用: 需要使用
Context.current().wrap(Runnable)或Context.current().makeWrapped()来确保上下文能跨越线程边界传递。 - 消息队列(Kafka/RabbitMQ): SDK 提供了相应的库来从消息头部注入和提取上下文。
- 跨线程/异步调用: 需要使用
-
数据采样: 在高并发下,全量采集会消耗大量资源和存储,需要合理配置采样策略:
- 头部采样(Head-based Sampling): 在请求入口处决定是否采样(如
1/100),最简单,但无法保证慢请求或错误请求被采样。 - 尾部采样(Tail-based Sampling): 等请求结束后,根据结果(如耗时、错误状态)决定是否保留,更智能,但实现复杂,需要缓存。
- 概率采样(Probabilistic Sampling): 按固定概率(如 1%)采样,是生产环境最常用的平衡方案。
- 推荐策略: 使用概率采样(如 1%-10%)作为基础,配合 尾部采样 保留所有错误和慢 Trace。
- 头部采样(Head-based Sampling): 在请求入口处决定是否采样(如
高级实践:关联业务上下文
全链路追踪不仅能帮你看到调用链,还能帮你快速定位问题。
- 在业务日志中打印 TraceID 和 SpanID: 将 MDC(Mapped Diagnostic Context)中的
trace_id和span_id自动插入到日志输出中,这样,当你发现一个错误日志时,可以根据 TraceID 轻松找到该请求的所有相关 Span 和日志。 - 添加业务标签(Tag):
// 在业务代码中添加自定义标签 Span span = Span.current(); span.setAttribute("user.id", userId); span.setAttribute("order.id", orderId); span.setAttribute("payment.amount", 100.50);这样,在 Jaeger/SigNoz UI 中就可以直接根据
user.id或order.id搜索对应的 Trace。
总结与建议
- 首选 OpenTelemetry:它是未来的方向,能统一你的数据采集标准,避免锁定。
- 从简单的做起:如果团队人手紧缺,可以直接用 SigNoz 或 商业 APM,把精力集中在业务优化上。
- 逐步推进:先从核心的、高流量的几个服务开始接入,验证效果后再扩展到所有服务。
- 关注上下文传播:这是系统能否正常工作的关键,特别是跨线程、跨进程(如异步任务、消息队列)时。
- 合理配置采样:不要试图采集所有数据,这会压垮你的系统,采用概率+尾部采样策略。
- 与日志、监控系统联动:全链路追踪不是孤岛,将 TraceID 关联到日志(如通过 Grafana Loki 或 Elasticsearch),与指标监控(如 Prometheus + Grafana)集成,才能形成立体的可观测性(Metrics、Logs、Traces 三大支柱)。