前言
1.分布式追踪
分布式追踪是一种用来观察、分析分布式系统中一次请求在多个服务之间调用路径的技术。
分布式追踪的核心概念
Trace(跟踪/链路)
Trace = 一次业务请求的完整链路。
例如:用户下单从开始到结束的完整路径。
Trace 由多个 Span 组成。
Span(跨度/片段)
Span = 一个独立的操作单元(一次HTTP调用、一次DB查询、一次消息消费等)。
每个 Span 代表一次小的操作,并记录:
- 开始时间 / 结束时间
- 耗时
- 操作名称(如 GET /api/order)
- 标签(Tag)
- 日志事件(Event)
- 状态(成功/失败)
- 父子关系(由调用链决定)
Span Context(Span 上下文)
Span Context 包含:
- TraceId
- SpanId
- 是否采样标志
- Baggage(可选)
Span Context 的最重要作用:跨进程传递上下文。
例如:
- 服务 A 调用服务 B,用 HTTP header 传递 TraceId
- 服务发送消息时,把 TraceId 写到 MQ 的 headers
- 服务之间日志输出时,需要记录 TraceId
常见的 HTTP Header 格式:
traceparent: 00-4bf9b58b5...-00f067aa0...-01
Propagation(上下文传递)
Propagation 就是:把 Trace 上下文从一个服务传递给另一个服务,让链路不断裂。
常见的方式:
- HTTP 请求:自动传递
traceparent - RabbitMQ / Kafka 消息:手动把 trace 写入 Message Headers
- gRPC:自带 metadata
Sampling(采样)
由于 Trace 数据量可能非常大,生产不会采集所有数据。
采样的作用:
- 控制数据量
- 降低存储压力
- 避免影响性能
常见采样方式:
- AlwaysOn(开发环境)
- TraceIdRatioBased(按比例,例如 10%)
- ParentBased(若父采样,则继续采样)
- Tail-based(Collector 后端动态采样)
分布式追踪就是“把一次请求在所有服务中的执行链路画出来”,通过 Trace → Span → Context → Propagation → Sampling 来实现。
2.RabbitMQ.Client:7.2.0
一开始我以为 RabbitMQ.Client 也可以实现链路追踪,只需使用 ActivitySource 和 TextMapPropagator 就能完成。但实际上,它本身不支持追踪上下文的自动注入/提取。要实现分布式追踪,必须手动使用 Activity 与 Propagator 将 trace context 写入消息头,并在消费者端提取。
在实践中,我使用 RabbitMQ.Client 7.2.0 完成了消息的生产与消费,并成功实现了手动注入/提取 trace context。一开始我也不知道实现不了(应该也能实现,Aspire.RabbitMQ.Client也是对RabbitMQ.Client的封装,可能我没找到方法),这个过程对理解分布式追踪机制非常有帮助,这里也记录一下。
如果想使用RabbitMQ.Client:7.2.0最新版本的完成RabbitMQ的操作,这个也挺有参考价值
此外,eshop 中的 Aspire.RabbitMQ.Client 也是基于 RabbitMQ.Client 实现的,依赖的版本是 6.8.1。我使用的是最新版本7.2.0。

2.1RabbitMQ链路追踪工具类(RabbitMQTelemetry)
RabbitMQTelemetry直接复制eshop的,只是把属性和方法都变成static,以便生产者和消费者共享同一个 ActivitySource 和 TextMapPropagator:
- ActivitySource:用于在应用中创建
Activity实例,代表一个链路或操作的追踪节点,是生成分布式追踪数据的入口。- 每次通过
ActivitySource创建的Activity,对应分布式追踪中的一个 Span,表示一次操作或处理单元。 - Activity 的
ParentId和TraceId用于构建整个 Trace 的调用链。
- 每次通过
- TextMapPropagator:用于在消息或请求的上下文中注入和提取 trace context,实现跨进程/跨服务的链路追踪传递。
TextMapPropagator用于跨进程、跨服务传递 TraceContext / SpanContext,确保在不同服务间能够延续同一个 Trace。- 在 RabbitMQ 场景下,就是把 trace 信息注入消息 header,然后消费端提取,保持链路连续性。
ActivitySource 对应每个操作的 Span,TextMapPropagator 对应跨服务传递 TraceContext,实现整个 Trace 的延续。

2.2生产端(Producer)
环境是Net9,需要安装以下2个包:
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.14.0" />
<PackageReference Include="RabbitMQ.Client" Version="7.2.0" />

RabbitMQ基本配置

OpenTelemetry链路追踪相关配置,基本和eshop配置基本相同,就是使用了AddConsoleExporter把OpenTelemetry 收集到的追踪数据打印到控制台,用于调试和验证链路追踪。
| 代码部分 | OpenTelemetry 概念 | 分布式追踪对应概念 |
|---|---|---|
| ActivitySource | Trace 生成器 | Span 的创建入口 |
| ActivityKind = Producer | Span 类型 | 标识操作角色(生产者/消费者) |
| TextMapPropagator | Context Propagation | 跨进程传递 TraceContext/SpanContext |
| TracerProvider | Trace 管理器 | 收集、采样、导出所有 Span |
| AddConsoleExporter | Exporter | Span 导出方式(这里是打印到控制台) |
| AlwaysOnSampler | Sampler | Span 是否被采集(AlwaysOn = 全采样) |

RabbitMQ.Client 7.2.0 已全面支持异步 API,这与旧版本最大的区别之一,所以RabbitMQ实现也都用了异步方法
这部分主要就是配置链接、创建链接、创建信道、声明交换机、声明队列、绑定交换机和队列
是否需要 routingKey 取决于交换机类型:Direct 和 Topic 必须;Fanout 和 Headers 不需要。

Console.ReadLine():从控制台读取要发送的消息
activitySource.StartActivity:创建并启动一个 Activity(对应 tracing 中的一个 Span),启动并创建Span。
activityName(比如"SendMessage")标识该操作;activityKind(通常为ActivityKind.Producer)表示这是消息生产者的 Span。
Activity.Current = activity :把当前 Activity 设置为全局上下文,方便后续 Propagator/其他代码读取(不是必须,但常用)。把刚创建的 Span 设为当前执行上下文,使日志、Propagator、Baggage、Exporter 都能正确继承这个 Span,从而保证完整的分布式追踪链路。
Encoding.UTF8.GetBytes(message):把消息字符串编码为字节数组,供 BasicPublishAsync 发送。BasicProperties:properties(实现 IBasicProperties)包含 RabbitMQ 消息元数据:content-type、delivery-mode、headers 等。
1= 非持久(transient)。2= 持久(persistent)。当队列是持久的并且DeliveryMode = 2时,消息在服务器重启后仍可恢复。Headers:一个IDictionary<string, object>,用于携带自定义键值(例如 trace context)。约定:通常把 propagator 注入的字符串值以byte[](Encoding.UTF8.GetBytes(value))形式存入,因为 RabbitMQ 的 header 在传输中可能被编码为byte[]或ReadOnlyMemory<byte>。消费端在提取时必须能兼容byte[]、ReadOnlyMemory<byte>、string等类型并正确解码

propagator.Inject(...):将当前 span 的上下文写入消息 headers。
PropagationContext(activity.Context, Baggage.Current)activity.Context= 当前 Span 的 TraceId + SpanId + TraceFlagsBaggage.Current= 当前链路的附加信息
properties= RabbitMQ 消息属性(BasicProperties)InjectTraceContext= 写入 headers 的委托函数,一个泛型参数和2个string参数

InjectTraceContext:定义 TraceContext 注入方法
propagator.Inject:把 TraceContext(traceId、spanId、baggage 等)写入 RabbitMQ 消息的 headers 中,方便下游消费者提取。
RabbitMQTelemetry.SetActivityContext:设置 Activity 的标签
channel.BasicPublishAsync:异步发布消息

2.3消费端(Consumer)
前面RabbitMQ、OpenTelemetry部分和生产端都一样,这里直接过了

这部分就是异步创建消费对象,ReceivedAsync就是接收到消息的回调,主要看就收到消息如何从消息头中获取TraceContext

Extract:从传输载体(carrier,比如 HTTP headers、RabbitMQ Headers)中读取 trace 信息(traceId, spanId, trace flags, baggage):
-
PropagationContext context:当前已有的上下文 -
T carrier:消息载体,是你要从中提取 trace context 的对象。类型泛型 T,可以是:IReadOnlyBasicProperties(RabbitMQ 消息属性)IDictionary<string, string>(HTTP headers)- 它承载了 trace 信息,需要通过 getter 提取
-
Func<T, string, IEnumerable<string>?> getter:- 从 carrier 中按 key 获取值
T= carrierstring= header/key 名称(比如traceparent或自定义 key)- 返回:
IEnumerable<string>?= 可能有多个值,返回 null 表示该 key 不存在
-
返回值:
PropagationContextActivityContext(traceId, spanId, trace flags)Baggage(可选上下文信息,用于跨服务携带额外数据)

ExtractTraceContext:提取tracecontext的委托方法
propagator.Extract:从RabbitMQ的消息属性BasicProperties中获取消息头Headers中的tracecontext(traceId、spanId、baggage)。这里的ea.BasicProperties是IReadOnlyBasicProperties,不同版本的RabbitMQ.Client可能参数类型有差别
activitySource.StartActivity:创建消费端的 Activity(即 Span),关联父 Span(parentContext.ActivityContext),延续生产端链路
SetActivityContext:给 Span 打标签(Metadata)
Encoding.UTF8.GetString(ea.Body.ToArray()):生产端发送的是byte字节,这里需要转换成字符串

channel.BasicConsumeAsync:将消费者绑定到队列,开始异步消费

2.4测试
首先确定自己的RabbitMQ可用

Telemetry.Producer和Telemetry.Consumer都是控制台,启动编译后的exe即可

使用生产端发送个消息,客户端也接收到了,但是他俩的Activity.TraceId不一样,说明生产和消费不是一个链路,结果还是失败了,但是这个思路是对的,是RabbitMQ.Clent不支持消息头写入TraceContext,后面使用Aspire.RabbitMQ.Clent就没这个问题。
这个使用最新版RabbitMQ.Clent,还是有参考意义的
RabbitMQ.Client 本身不支持自动链路追踪,消息头不会自动写入 TraceContext,需要自己用 Activity + Propagator 手动注入和提取,或者使用 Aspire.RabbitMQ.Client,后面我们就使用Aspire.RabbitMQ.Client完成RabbitMQ的分布式链路追踪

2.5总结
这个只是使用了最新的RabbitMQ.Client:7.2.0包完成RabbitMQ功能,也算试错,我觉得思路也没问题
3.Aspire.RabbitMQ.Client
如果使用最新版本的Aspire.RabbitMQ.Client,他的RabbitMQ.Client也有些问题(也不一定是包的问题,我没试出来,所以还是决定以eshop为主),所以我这里包版本和eshop保持一致
eshop的包版本管理文件是Directory.Packages.props

代码大部分还是参考了eshop,少部分进行了修改
3.1Activity设置标签扩展方法
这个是异常时设置Activity标签

3.2RabbitMQ配置
SubscriptionClientName:订阅客户端名称,就是当前服务的队列名称
RetryCount:重试次数,发布消息时使用Polly.Core进行失败重试

3.3发布消息接口(IEventBus)
IEventBus:发布消息接口,方便DI注入,PublishAsync接收string消息和路由键(routingKey),路由键(routingKey)主要是为了确定把消息发给指定的消费端

3.4RabbitMQ服务类(RabbitMQEventBus)
主要是初始化RabbitMQ,完成RabbitMQ生产和发布功能,去掉了eshop事件的概念

3.4.1重试管道(CreateResiliencePipeline)
通过 ResiliencePipeline 构建了一个可重试的消息发布管道:当发生 BrokerUnreachableException 或 SocketException 等网络异常时,会根据指数退避策略(延迟时间随重试次数呈指数增长)自动重试,最多重试 retryCount 次,从而提高 RabbitMQ 消息发布的可靠性。

3.4.2发布消息(PublishAsync)
Encoding.UTF8.GetBytes(message):RabbitMQ 消息体是 byte[],这里把字符串消息编码成 UTF-8。
_pipeline.ExecuteAsync:使用通过 Polly 或 .NET Resilience 构建的 重试管道。在网络异常或 RabbitMQ Broker 不可达时,会按 指数退避 自动重试。ct 是内部 CancellationToken,可中途取消。

确保消息发布者(Publisher)有一个可用的通道(Channel),并且开启消息确认机制(Publisher Confirms),同时注册事件处理消息确认或未确认的回调。
_publisherChannel?.Dispose():如果之前的_publisherChannel存在(可能已经关闭或异常),先释放它的资源。ispose()会释放通道占用的底层资源(TCP、内存),防止资源泄露。?.运算符表示如果_publisherChannel为 null 就跳过,不会报空引用异常。connection.CreateModel():通过_connection(IConnection对象)创建新的 Channel。Connection是 TCP 连接,是重量级对象,通常一个应用只建立少量连接。Channel是轻量级对象,代表逻辑通道,可以并行使用。
_publisherChannel.ConfirmSelect():开启 Publisher Confirms 模式。- 当 RabbitMQ 成功收到消息并写入队列后,会发送 ACK 给 Publisher。
- 如果消息无法入队(比如队列不存在),会发送 NACK。
- 使用 Confirm 模式,可以确保消息不会丢失,提高可靠性。
BasicAcks:当消息被 RabbitMQ 确认(ACK)时触发。日志记录为 Trace 级别,表示消息已确认。BasicNacks:当消息被 RabbitMQ 拒绝(NACK)时触发。日志记录为 Warning 级别,表示消息未成功入队,可能需要重试或处理异常。

BasicProperties:用于存放消息元信息。
DeliveryMode = 2:表示消息持久化,RabbitMQ 重启后仍存在。
_propagator.Inject:TraceContext 注入,实现链路追踪跨进程传递。
Activity.Current?.Context ?? default:获取当前 Activity 的 TraceContext。Baggage.Current:当前上下文的可传播信息。
_activitySource.StartActivity:创建生产者 Activity(Span)
ActivityKind.Producer表示生产者发送消息。SetActivityContext设置 RabbitMQ 相关标签
_publisherChannel.BasicPublish:发送消息到指定交换机 (ExchangeName)。
- 使用
routingKey指定路由。 mandatory: true:如果消息未能路由到任何队列,会返回给生产者。basicProperties包含元信息和 TraceContext。body是消息内容。

3.4.3初始化消费者(ExecuteAsync)
通过继承后台服务(BackgroundService),重写ExecuteAsync方法完成初始化消费者,并绑定消息接收事件(OnMessageReceived)

检查当前 RabbitMQ 连接是否存在且可用,如果不可用则通过依赖注入的连接工厂创建新连接,并订阅关闭事件以便连接断开时能重置 _connection。

确保 RabbitMQ 消费者通道可用:如果通道不存在或关闭,则创建新通道、声明交换机和队列、绑定路由,并注册异常回调以便出错时重建通道。

在后台服务中持续确保 RabbitMQ 连接和消费者通道可用,注册异步消息接收器并开始消费,直到服务被取消。
_consumerChannel.BasicConsume:
queue:指定要消费的队列名称。autoAck:是否自动确认消息:true:RabbitMQ 在发送消息后立即标记为已确认(不可靠,如果处理失败消息会丢失)。false:需要消费者手动调用BasicAck来确认消息已成功处理,适合保证消息不丢失。
consumer:IBasicConsumer实例,定义了收到消息时的处理逻辑(例如AsyncEventingBasicConsumer并绑定了Received事件)。

3.4.4消息处理(OnMessageReceived)
Encoding.UTF8.GetString(e.Body.Span):将 RabbitMQ 消息的二进制 Body 转为 UTF-8 字符串。
_propagator.Extract:OpenTelemetry Trace 上下文提取
_propagator:TextMapPropagator,用于在分布式系统中传递 trace 上下文。Extract从消息属性BasicProperties.Headers中读取 trace 信息。- 如果消息头包含 trace key,则解码为字符串数组返回;否则返回空数组。
Baggage.Current = parentContext.Baggage:将从消息中提取的 Baggage(上下文键值对) 设置为当前线程的 Baggage。Baggage 可用于跨服务传递额外业务信息(如用户 ID、请求 ID 等)。
_activitySource.StartActivity:创建一个 OpenTelemetry Activity 用于追踪消费者操作
ActivityKind.Consumer表示这是一个消息消费操作。parentContext.ActivityContext指定父 trace,上下文来自生产者。SetActivityContext:为 Activity 添加 RabbitMQ 特定标签(如队列名、路由键、操作类型)。activity.SetTag("message", message):记录消息内容,可用于调试或追踪。
BasicAck:通知 RabbitMQ 消息已被成功消费,可以从队列中删除。
e.DeliveryTag:消息唯一标识。false:表示只确认当前消息,不批量确认。
BasicNack:通知 RabbitMQ 消息处理失败。
e.DeliveryTag:消息唯一标识。- 第一个
false:只针对当前消息。 - 第二个
false:不重新入队(如果是true,消息会重新进入队列等待再次消费)。
_propagator.Extract是如何知道要从消息中提取哪些分布式链路的 key 的?
TextMapPropagator的作用:
- 它是 OpenTelemetry 用于跨进程/跨服务传递 trace 上下文的工具。
- 可以从消息、HTTP header 等载体中读取 trace 信息,恢复
PropagationContext(包含 ActivityContext 和 Baggage)。key 是由 propagator 内部决定的:
- W3C TraceContext propagator 会查找
"traceparent"和"tracestate"。- Baggage propagator 会查找
"baggage"。- 你不需要自己写这些 key,propagator 会依次调用你提供的 getter 函数并传入它需要的 key。
这里先了解概念,下面运行示例的时候调试看看

3.4.5总结
RabbitMQEventBus主要实现了生产/消费功能,每个服务即是生产者发布事件,也是消费者订阅事件
总结try 分布式追踪依赖注入扩展(RabbitMQEventBusExtensions`)
AddRabbitMQClient:注册RabbitMQ客户端
AddOpenTelemetry:注册 OpenTelemetry Tracing(分布式追踪)
AddSource:指定追踪来源,即你事件总线内部创建的ActivitySource。AlwaysOnSampler:始终采样,所有消息都会生成 trace。AddConsoleExporter:将 trace 输出到控制台,用于调试或观察链路。
剩下的都是我们自定义的RabbitMQ的服务和配置了,留意分布式链路追踪相关代码即可

3.6RabbitMQ 链路追踪工具类(RabbitMQTelemetry)
分布式链路精华就是下面俩句话,理解ActivitySource和TextMapPropagator就没啥问题了
ActivitySource:创建 ActivitySource 实例,用于生成 Activity(即 trace 节点)。Activity 是 OpenTelemetry 中的基本追踪单元,对应一个操作(发送消息、消费消息等)。生产者和消费者共享同一个 ActivitySource,保证 trace 的一致性。
TextMapPropagator:全局 Propagator,用于 注入和提取 trace 上下文(TraceContext)。DefaultTextMapPropagator 通常包含 W3C TraceContext 和 Baggage。在消息发送端使用 Inject 将 trace 写入消息头;在消费端使用 Extract 提取。
SetActivityContext:给 Activity 添加 标准化标签(Semantic Tags),符合 OpenTelemetry 消息系统规范。

3.7生产端(RabbitMQ.Producer.Web)
注册时间总线
启用OpenApi/Scalar,创建测试接口,用于发布消息

使用MiniApi创建一个发布事件的接口,当前是生产端,发给消费端,所以路由键(routingKey)是Consumer

appsettings.json配置:
ConnectionStrings:EventBus:RabbitMQ链接地址
EventBus:SubscriptionClientName:
- 当前服务绑定的队列名称
- 当前服务在
RabbitMQ绑定队列时,也使用此值作为路由键(routingKey)

3.8消费端(RabbitMQ.Consumer.Web)
生产和消费端的配置基本一样,主要就是消费端的发布消息的路由键(routingKey)是Producer,因为他要发给生产端

EventBus:SubscriptionClientName:
- 当前服务绑定的队列名称
- 当前服务在
RabbitMQ绑定队列时,也使用此值作为路由键(routingKey)

3.9测试
在生产端目录下启动生产端
dotnet run --urls http://localhost:5200/

在消费端目录下启动消费端
dotnet run --urls http://localhost:5201/

查看RabbitMQ管理界面:
- 交换机(
Exchange: rabbitmq_trace_bus)成功创建 - 队列(
Consumer)成功创建,绑定路由键(Consumer) - 队列(
Producer)成功创建,绑定路由键(Producer)

从生产端发个消息(5200)

消费端收到消息,而且Activity.TraceId一样,说明他们是一个链路
Published: 你好啊
Activity.TraceId: aedaf8a4d36b998a35f56dfa55949331
Activity.SpanId: Received: 你好啊
Activity.TraceId: aedaf8a4d36b998a35f56dfa55949331
Activity.SpanId: 729ac7ae296d99b9

我们再从消费端给生产端发送消息,Activity.TraceId也一样
其实每个微服务不区分生产端和消费端,每个微服务即可生产消息,也可消费消息,这里的生产/消费只是我为了更好理解才这样命名的
Received: nice to meet you
Activity.TraceId: cbbdd688503dd2a59bf78ccb48039efd
Activity.SpanId: bbe8554e42ca7377Published: nice to meet you
Activity.TraceId: cbbdd688503dd2a59bf78ccb48039efd
Activity.SpanId: 6e3201cece790fe2

生产端也收到了消息

3.10traceparent/tracestate/baggage
我们上面学习的时候留了个问题,想知道_propagator.Inject和_propagator.Extract到底注入和获取了那些字段,这里代码我们已经调通了,然后直接在俩个方法传入的委托中输出日志

随便发送一条消息,发现默认只注入了traceparent字段

W3C 规定的 HTTP / Message 头格式是:
| Header | 是否必须 | 用途 | 作用(解释) |
|---|---|---|---|
| traceparent | 必须 | 传递 TraceId / SpanId / Flags | 跨服务传递分布式追踪的核心上下文,让各个服务的 Trace 能串起来 |
| tracestate | 可选 | Vendor 扩展,通常不需要 | 为各厂商(如 Jaeger、Zipkin、Datadog)提供额外信息,保持多系统互通 |
| baggage | 可选 | 业务透传,不属于 TraceContext | 携带自定义业务键值对(如 userId、tenantId),在链路中的所有服务都能读到 |
也就是tracestate/baggage默认不需要传递,但是既然学到这里了,就看看如何传递tracestate/baggage
在发布消息的时候设置tracestate/baggage
// 创建生产者 Activity
using var activity = _activitySource.StartActivity($"{routingKey} publish", ActivityKind.Producer);
// 设置tracestate
activity.TraceStateString = "vendor=jaeger,env=dev,tenant=acme";var properties = _publisherChannel.CreateBasicProperties();
properties.DeliveryMode = 2; // 持久化消息// 设置Baggage
properties.Headers ??= new Dictionary<string, object>();
Baggage.Current = Baggage.Current.SetBaggage(new[]
{new KeyValuePair<string, string>("userId", "123"),new KeyValuePair<string, string>("name", "peng"),new KeyValuePair<string, string>("age", "18"),
});

在接收消息时获取
Baggage.Current = parentContext.Baggage;// 创建消费者 Activityusing var activity = _activitySource.StartActivity($"{e.RoutingKey} receive", ActivityKind.Consumer, parentContext.ActivityContext);// 获取tracestateConsole.WriteLine($"activity.TraceStateString {activity.TraceStateString}");// 获取baggageforeach (var kv in Baggage.Current.GetBaggage()){Console.WriteLine($"[Receive:baggage] {kv.Key}:{kv.Value}");}

启动项目,查看运行结果,traceparent/tracestate/baggage正常传递
生产日志
Inject Key:traceparent Value:00-cfc939435a72cde00a66bec3701b29b6-094e0f2ddd0dfce8-01
Inject Key:tracestate Value:vendor=jaeger,env=dev,tenant=acme
Inject Key:baggage Value:userId=123,name=peng,age=18
Published: peng
Activity.TraceId: cfc939435a72cde00a66bec3701b29b6
Activity.SpanId: 094e0f2ddd0dfce8
Activity.TraceFlags: Recorded
Activity.TraceState: vendor=jaeger,env=dev,tenant=acme
Activity.ParentSpanId: 5d5fa5b9a2c686e5
Activity.DisplayName: Consumer publish
Activity.Kind: Producer
Activity.StartTime: 2025-11-23T15:03:46.4822694Z
Activity.Duration: 00:00:00.0052031
Activity.Tags:messaging.system: rabbitmqmessaging.destination_kind: queuemessaging.operation: publishmessaging.destination.name: Consumermessaging.rabbitmq.routing_key: Consumer
Instrumentation scope (ActivitySource):Name: Peng.RabbitMQ.Telemetry
Resource associated with Activity:telemetry.sdk.name: opentelemetrytelemetry.sdk.language: dotnettelemetry.sdk.version: 1.12.0service.name: unknown_service:RabbitMQ.Producer.Web
消费日志
Extract Key:traceparent Value:00-cfc939435a72cde00a66bec3701b29b6-094e0f2ddd0dfce8-01
Extract Key:tracestate Value:vendor=jaeger,env=dev,tenant=acme
Extract Key:baggage Value:userId=123,name=peng,age=18
activity.TraceStateString vendor=jaeger,env=dev,tenant=acme
[Receive:baggage] userId:123
[Receive:baggage] name:peng
[Receive:baggage] age:18
Received: peng
Activity.TraceId: cfc939435a72cde00a66bec3701b29b6
Activity.SpanId: 0206598120b35de8
Activity.TraceFlags: Recorded
Activity.TraceState: vendor=jaeger,env=dev,tenant=acme
Activity.ParentSpanId: 094e0f2ddd0dfce8
Activity.DisplayName: Consumer receive
Activity.Kind: Consumer
Activity.StartTime: 2025-11-23T15:03:46.4962270Z
Activity.Duration: 00:00:00.0035303
Activity.Tags:messaging.system: rabbitmqmessaging.destination_kind: queuemessaging.operation: receivemessaging.destination.name: Consumermessaging.rabbitmq.routing_key: Consumermessage: peng
Instrumentation scope (ActivitySource):Name: Peng.RabbitMQ.Telemetry
Resource associated with Activity:telemetry.sdk.name: opentelemetry

最后简单看一下traceparent,他是分布式链路的关键
traceparent的组成
traceparent: {version}-{trace-id}-{parent-id}-{trace-flags}:
- 00:版本号(version),目前是
00- cfc939435a72cde00a66bec3701b29b6:TraceId,16 字节(32 个十六进制字符),全局唯一标识一次分布式调用链
- 094e0f2ddd0dfce8:ParentId,8 字节(16 个十六进制字符),标识当前 span 的父 span
- 01:TraceFlags,表示这个 trace 是否被采样(01 = 已采样,00 = 未采样)
所以
traceparent长度比较长是正常的,因为 TraceId 是 16 字节、ParentId 8 字节,加上版本和标志,总长度就达到 55 个字符(不包括连字符-计数)。Extract Key:traceparent Value:00-cfc939435a72cde00a66bec3701b29b6-094e0f2ddd0dfce8-01 Activity.TraceId: cfc939435a72cde00a66bec3701b29b6 Activity.ParentSpanId: 094e0f2ddd0dfce8
3.11总结
Java的Spring Cloud生态链路追踪配置一下就可以实现了,Net生态确实不太好,还需要发展,一个链路追踪学了小三天,但是了解原理毕竟还是有好处的
补充:ehsop中OpenTelemetry也支持一些链路追踪,不用手动实现
- 自动追踪 HTTP 请求
- 自动追踪
gRPC客户端调用 - 自动追踪
HttpClient请求
📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!
💬 加入交流群(QQ群):576434538

