Http:
链路追踪的核心流程确实确实可以简化为这样的流程:从第一个服务生成全局唯一的 traceId 开始,通过拦截器在服务间传递 traceId 和 spanId,最终将这些信息写入日志,实现全链路的可视化和问题追踪。
可以再进一步拆解这个过程,让细节更清晰:
1. 首次请求:生成根 traceId 和 spanId
- 当用户请求首次到达服务 1(如网关或入口服务)时,Spring Cloud Sleuth 的拦截器(如
TracingFilter)会拦截请求,判断这是 “新请求”(无traceId)。 - 此时会生成全局唯一的
traceId(如a1b2c3d4)和第一个spanId(如s1,称为 “根 Span”),并将它们封装到SpanContext中,绑定到当前线程的ThreadLocal。 - MDC 写入:Sleuth 会自动将
traceId和spanId放入日志框架的 MDC(Mapped Diagnostic Context)中,因此日志中会自动带上这两个字段(需在日志格式中配置%X{traceId} %X{spanId})。
2. 服务间调用:传递 traceId 和 spanId
- 当服务 1需要调用服务 2时(如通过 HTTP/Feign):
- 拦截器会从当前线程的
SpanContext中取出traceId=a1b2c3d4和spanId=s1。 - 自动将这些信息写入 HTTP 请求头(如
X-B3-TraceId: a1b2c3d4、X-B3-SpanId: s1),随请求一起发送到服务 2。
- 拦截器会从当前线程的
- 服务 2接收请求时:
- 拦截器从请求头中解析出
traceId=a1b2c3d4和parentSpanId=s1(服务 1 的spanId)。 - 生成服务 2 自己的
spanId=s2,并将parentSpanId设置为s1,形成 “父子关系”。 - 同样将
traceId和s2写入 MDC,确保服务 2 的日志也携带相同的traceId。
- 拦截器从请求头中解析出
3. 多级调用:链路串联
- 当服务 2调用服务 3时,重复上述过程:服务 2 的
spanId=s2会作为parentSpanId传递给服务 3,服务 3 生成spanId=s3,traceId始终保持a1b2c3d4。 - 最终,整个链路的所有服务日志中,
traceId都是同一个,spanId则通过parentSpanId形成调用关系(s1 → s2 → s3)。
4. 日志与 Zipkin 展示
- 日志层面:通过 MDC,所有服务的日志都包含
traceId,可通过grep "a1b2c3d4"快速筛选出同一链路的所有日志,定位跨服务问题。 - Zipkin 层面:各服务会将
traceId、spanId、调用耗时等信息上报到 Zipkin 服务器,Zipkin 根据traceId聚合所有Span,生成可视化的调用链路图(包含每个服务的耗时、状态等)。
--------------------------------------
ZIPKIN原理:
Zipkin 作为分布式追踪系统,底层通过追踪数据的生成、传递、收集、存储和展示五个核心流程实现功能,其设计依赖分布式系统的追踪规范(如 OpenTracing)和高效的通信 / 存储机制。以下从底层原理拆解 Zipkin 的工作流程:
一、底层核心概念回顾
在深入底层前,先明确 3 个核心概念(Zipkin 基于 Google Dapper 论文设计):
Trace:一个分布式请求的完整链路,用全局唯一的traceId标识(如a1b2c3d4)。Span:链路中的一个 “操作单元”(如一次服务调用、一次数据库查询),用spanId标识,包含耗时、状态、标签等信息,通过parentSpanId关联父操作。Annotation:Span中的时间点标记(如cs客户端发送、sr服务端接收、ss服务端响应、cr客户端接收),用于计算耗时。
二、底层工作流程:从追踪生成到可视化
Zipkin 的底层流程可分为 5 个阶段:追踪数据生成 → 跨服务传递 → 本地收集 → 上报存储 → 查询展示。
1. 阶段 1:追踪数据生成(Span 创建)
当请求进入系统时,由应用中的追踪代理(如 Brave) 生成 Trace 和 Span 数据,核心逻辑:
- 根
Span生成:首个接收请求的服务(如网关)会创建根Span,生成全局唯一的traceId和根spanId(parentSpanId为null)。 - 子
Span生成:后续服务调用时,基于上游传递的traceId和parentSpanId,生成当前服务的spanId,并记录Annotation时间戳(如sr记录服务端接收时间,ss记录服务端响应时间,二者差值即为服务处理耗时)。 - 底层实现:由 Brave(Zipkin 的 Java 实现库)通过拦截器 / 切面自动完成,无需业务代码侵入。例如:
- HTTP 拦截器(
TracingFilter):拦截请求,在请求进入时创建Span,在响应时标记Span完成。 - 数据库拦截器(
TracingDataSource):拦截 SQL 执行,创建 “数据库操作Span”,关联到当前服务的Span。
- HTTP 拦截器(
2. 阶段 2:跨服务传递(SpanContext 传播)
traceId、spanId 等核心信息(封装为 SpanContext)需跨服务传递,底层依赖协议注入:
- 传递载体:根据通信协议,将
SpanContext注入到请求的 “元数据” 中:- HTTP/RPC:注入请求头(如
X-B3-TraceId、X-B3-SpanId,遵循 B3 协议)。 - 消息队列:注入消息的
Headers(如 Kafka 的headers字段、RabbitMQ 的messageProperties)。 - 自定义协议:需手动注入到协议的扩展字段中(如 Socket 消息的额外字段)。
- HTTP/RPC:注入请求头(如
- 底层实现:Brave 提供
Propagator接口,不同协议有对应的实现(如HttpPropagator处理 HTTP 头,KafkaPropagator处理 Kafka 消息),负责SpanContext的 “注入”(发送端)和 “提取”(接收端)。
3. 阶段 3:本地追踪数据收集
每个服务生成的 Span 数据会先在本地暂存,等待批量上报,底层通过线程安全的缓冲区实现:
- 本地存储:
Span数据生成后,先存储在内存缓冲区(如ConcurrentLinkedQueue),避免频繁 IO 影响性能。 - 批量处理:缓冲区达到阈值(如 100 条)或定时(如 1 秒)触发批量处理,将
Span数据序列化为 JSON 格式。 - 采样机制:为避免高并发下的数据量过大,Zipkin 默认采用采样策略(如 1/1000 采样率),仅上报部分
Trace。采样决策通过Sampler接口实现,可自定义(如对特定用户 ID 全量采样)。
4. 阶段 4:追踪数据上报与存储
本地收集的 Span 数据会上报到 Zipkin 服务器,服务器将其持久化存储,底层依赖高效的网络传输和存储引擎:
- 上报方式:应用通过 HTTP 或 Kafka 向 Zipkin 服务器发送数据:
- HTTP 上报:简单直接,适合小规模场景(默认方式)。
- Kafka 上报:通过消息队列异步上报,适合高并发场景(避免 Zipkin 服务器压力过大)。
- 服务器接收:Zipkin 服务器(基于 Spring Boot 实现)提供
/api/v2/spans接口,接收 JSON 格式的Span数据,解析后写入存储。 - 存储引擎:Zipkin 支持多种存储后端(可配置):
- 内存存储:仅用于测试,服务重启后数据丢失。
- Elasticsearch:适合大规模、高可用场景(生产环境常用)。
- MySQL:适合小规模、数据量不大的场景。
- Cassandra:适合写入密集型场景。存储时,
Span数据按traceId索引,方便后续查询。
5. 阶段 5:查询与可视化展示
用户通过 Zipkin UI 查询追踪数据,底层依赖索引查询和链路聚合:
- 查询接口:Zipkin 服务器提供
/api/v2/trace/{traceId}等接口,根据traceId从存储中查询所有关联的Span数据。 - 链路聚合:查询到的
Span数据按parentSpanId排序,构建出完整的调用链路(如Span1 → Span2 → Span3)。 - UI 展示:前端页面(基于 React)将聚合后的链路可视化,展示每个
Span的耗时、状态、标签等信息,支持按服务名、时间范围筛选。
三、核心组件与技术栈
Zipkin 底层依赖以下关键组件和技术:
- 客户端(应用侧):
- Brave:Java 语言的追踪库,负责
Span生成、SpanContext传递、数据本地收集。 - 拦截器 /instrumentation:对常见框架(Spring MVC、Feign、MySQL、Kafka 等)的拦截器,实现自动追踪。
- Brave:Java 语言的追踪库,负责
- 服务端(Zipkin 服务器):
- 基于 Spring Boot 开发,提供 HTTP 接口接收数据。
- 存储模块:抽象出
StorageComponent接口,适配 Elasticsearch、MySQL 等存储。 - 查询模块:基于存储实现
TraceQueryService,支持按traceId、服务名等查询。
- 通信协议:
- B3 协议:定义
traceId、spanId在 HTTP 头中的传递格式(主流分布式追踪通用协议)。 - JSON:
Span数据的序列化格式(简单易解析)。
- B3 协议:定义
四、底层设计的核心目标
Zipkin 底层设计围绕两个核心目标:
- 低侵入性:通过拦截器和 instrumentation 实现自动追踪,无需修改业务代码。
- 高性能:通过采样机制、本地缓冲、批量上报减少对应用性能的影响(通常 overhead < 1%)。
总结
Zipkin 底层通过 “追踪代理生成数据 → 协议注入传递上下文 → 本地缓冲批量上报 → 服务器存储与聚合 → UI 可视化展示” 的流程,实现分布式链路的追踪。核心依赖 Brave 库的自动拦截、B3 协议的跨服务传递、以及可扩展的存储引擎,最终为开发者提供跨服务问题排查和性能分析的能力。
-------------------------------------------
RPC协议怎么处理:
在 RPC 调用(如 Dubbo、gRPC 等)中,traceId 等追踪信息的传递方式确实与 HTTP 不同(不依赖 HTTP 头),但核心思路一致:通过 RPC 协议的 “附加元数据” 机制,将 SpanContext(包含 traceId、spanId 等)从服务 1 传递到服务 2。不同 RPC 框架的具体实现细节略有差异,但底层原理相通。
一、RPC 传递 traceId 的核心原理
RPC 框架(如 Dubbo、gRPC)通常会定义 **“调用上下文”**(用于传递非业务数据的附加信息),traceId 等追踪信息就通过这个上下文传递。具体流程如下:
-
服务 1(调用方)发送 RPC 请求时:
- 追踪框架(如 Sleuth + Brave)拦截 RPC 调用,从当前线程的
SpanContext中提取traceId、spanId、parentSpanId等信息。 - 将这些信息写入 RPC 框架的 “附加元数据”(如 Dubbo 的
RpcContext、gRPC 的Metadata)中,随业务请求一起发送。
- 追踪框架(如 Sleuth + Brave)拦截 RPC 调用,从当前线程的
-
服务 2(接收方)处理 RPC 请求时:
- 追踪框架拦截 RPC 请求,从 “附加元数据” 中解析出
traceId、spanId等信息,重建SpanContext。 - 基于重建的
SpanContext生成服务 2 的子Span(parentSpanId设为服务 1 的spanId),确保链路关联。
- 追踪框架拦截 RPC 请求,从 “附加元数据” 中解析出
二、主流 RPC 框架的具体实现(以 Dubbo 和 gRPC 为例)
1. Dubbo 框架中传递 traceId
Dubbo 是 Java 生态常用的 RPC 框架,通过 RpcContext 传递附加信息,追踪框架(如 Sleuth)会自动集成 Dubbo 的拦截器实现 traceId 传递。
(1)依赖集成(Spring Cloud 环境)
需引入 Sleuth 对 Dubbo 的集成依赖,自动拦截 Dubbo 调用:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-dubbo</artifactId>
</dependency>
(2)底层传递机制
-
调用方(服务 1):Dubbo 的
Invocation拦截器(TracingFilter)会在发送请求前,将SpanContext信息(traceId、spanId等)放入RpcContext的attachments(附加参数)中:java
运行
// 伪代码:Dubbo 调用方拦截器 public class DubboTracingFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 从当前线程获取 SpanContext SpanContext context = tracer.currentSpan().context(); // 将 traceId、spanId 放入 RpcContext 附加参数 RpcContext.getContext().setAttachment("X-B3-TraceId", context.traceIdString()); RpcContext.getContext().setAttachment("X-B3-SpanId", context.spanIdString()); // 继续执行 RPC 调用 return invoker.invoke(invocation); } } -
接收方(服务 2):接收方的拦截器从
RpcContext的attachments中提取traceId等信息,重建SpanContext并生成子Span:java
运行
// 伪代码:Dubbo 接收方拦截器 public class DubboServerTracingFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 从 RpcContext 附加参数中提取 traceId、spanId String traceId = RpcContext.getContext().getAttachment("X-B3-TraceId"); String spanId = RpcContext.getContext().getAttachment("X-B3-SpanId"); // 重建 SpanContext SpanContext context = TraceContext.newBuilder() .traceId(TraceId.fromString(traceId)) .spanId(SpanId.fromString(spanId)) .build(); // 生成子 Span 并绑定到当前线程 Span childSpan = tracer.newChild(context).start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(childSpan)) { return invoker.invoke(invocation); // 处理业务逻辑 } finally { childSpan.finish(); } } } -
效果:服务 2 的日志和追踪数据中,
traceId与服务 1 一致,spanId关联到服务 1 的spanId,实现链路串联。
2. gRPC 框架中传递 traceId
gRPC 是跨语言的 RPC 框架,通过 Metadata(元数据)传递附加信息,追踪框架(如 Brave)提供专门的拦截器支持。
(1)依赖集成
引入 Brave 对 gRPC 的集成依赖:
xml
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-grpc</artifactId>
</dependency>
(2)底层传递机制
-
调用方(服务 1):gRPC 的客户端拦截器(
TracingClientInterceptor)会将SpanContext信息写入Metadata(gRPC 的元数据载体):java
运行
// 伪代码:gRPC 客户端拦截器 public class TracingClientInterceptor implements ClientInterceptor { @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { // 获取当前 SpanContext SpanContext context = tracer.currentSpan().context(); // 创建 gRPC Metadata,写入 traceId、spanId Metadata metadata = new Metadata(); metadata.put(Metadata.Key.of("X-B3-TraceId", Metadata.ASCII_STRING_MARSHALLER), context.traceIdString()); metadata.put(Metadata.Key.of("X-B3-SpanId", Metadata.ASCII_STRING_MARSHALLER), context.spanIdString()); // 发送带 Metadata 的请求 return next.newCall(method, callOptions.withOption(MetadataConstants.HEADER_METADATA_KEY, metadata)); } } -
接收方(服务 2):服务端拦截器从
Metadata中提取信息,重建SpanContext并生成子Span:java
运行
// 伪代码:gRPC 服务端拦截器 public class TracingServerInterceptor implements ServerInterceptor { @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( ServerCall<ReqT, RespT> call, Metadata metadata, ServerCallHandler<ReqT, RespT> next) { // 从 Metadata 中提取 traceId、spanId String traceId = metadata.get(Metadata.Key.of("X-B3-TraceId", Metadata.ASCII_STRING_MARSHALLER)); String spanId = metadata.get(Metadata.Key.of("X-B3-SpanId", Metadata.ASCII_STRING_MARSHALLER)); // 重建 SpanContext 并生成子 Span SpanContext context = TraceContext.newBuilder() .traceId(TraceId.fromString(traceId)) .spanId(SpanId.fromString(spanId)) .build(); Span childSpan = tracer.newChild(context).start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(childSpan)) { return next.startCall(call, metadata); // 处理业务 } finally { childSpan.finish(); } } }
三、RPC 与 HTTP 传递方式的异同
| 维度 | HTTP 传递 | RPC 传递(如 Dubbo/gRPC) |
|---|---|---|
| 载体 | HTTP 头(Header) | RPC 附加元数据(Dubbo 的 RpcContext、gRPC 的 Metadata) |
| 拦截点 | HTTP 过滤器(如 TracingFilter) | RPC 框架的拦截器(如 Dubbo Filter、gRPC Interceptor) |
| 标准化 | 遵循 B3 协议(X-B3-* 头) | 通常也兼容 B3 协议(用相同的 key 如 X-B3-TraceId) |
| 侵入性 | 无(框架自动处理) | 无(集成追踪框架后自动处理) |
四、总结
RPC 调用中传递 traceId 的核心是利用 RPC 框架自身的 “附加元数据” 机制(如 Dubbo 的 RpcContext、gRPC 的 Metadata),通过追踪框架的拦截器自动注入和提取 SpanContext 信息。虽然载体与 HTTP 不同,但最终目的一致:确保 traceId 在跨服务调用中不中断,实现全链路追踪。
对于开发者而言,只需集成对应 RPC 框架的追踪插件(如 Sleuth-Dubbo、Brave-gRPC),即可自动完成 traceId 的传递,无需手动处理。

1万+

被折叠的 条评论
为什么被折叠?



