You need to enable JavaScript to run this app. Spring Sleuth Hoxton.SR9 RabbitMQ b3单头缺失ParentSpan咨询
优惠活动
大模型
产品
解决方案
定价
更多

Spring Sleuth Hoxton.SR9 RabbitMQ b3单头缺失ParentSpan咨询

Spring Cloud Sleuth 跨版本迁移B3头传播行为说明

应用从Spring Cloud Greenwich.SR2 迁移至 Hoxton.SR9 版本,目标是实现从REST请求入口触发、到最终投递RabbitMQ的全链路调用追踪。
两个版本的核心传播机制差异:

  • 旧Greenwich版本会传递3个独立消息头,分别存储trace、span、parentSpan信息
  • 新Hoxton版本默认仅传递单个b3头,格式为{traceId}-{spanId}-{sample}-{parentSpan},后两个字段为可选参数

遇到的异常表现

RabbitMQ队列中收到的消息携带的b3头始终不包含parentSpan字段,链路表现为:

  1. 应用REST控制器内部链路:traceId=T, spanId=S1, parentSpanId=S0
  2. 应用内部触发消息投递前的链路:traceId=T, spanId=S2, parentSpanId=S1
  3. RabbitMQ队列中的消息头:b3=T-S2-1

trace值传递正确,但消息头直接复用了应用内发消息上下文的span值,完全缺失parentSpan字段。按照旧版本的预期,消息投递时应当生成新的spanId,且消息头里的parentSpan值等于应用内发消息上下文对应的span值。
当前显式配置的Sleuth参数为:

spring.sleuth.enabled=true
spring.messaging.enabled=true
spring.supportsJoin=false

注:将supportsJoin恢复为默认值测试,同样无法解决parentSpanId缺失的问题。

问题根因定位

通过断点调试逐层追踪的逻辑链路:

  • 向AbstractMessageChannel.send()传入GenericMessage实例
  • 消息最终传递至TracingChannelInterceptor.preSend()方法
  • 该拦截器配置的DeferredInjector实例中,消息头写入逻辑的实现类为MessageHeaderPropagation,生产端的注入函数被默认设置为SINGLE_NO_PARENT
  • 拦截器执行注入逻辑时,DeferredInjector.inject(span.context(), headers) 会按照SINGLE_NO_PARENT模式向外发消息添加格式为{traceId}-{spanId}-1的b3头

根因明确:parentSpanId缺失是Hoxton版本默认的生产端注入逻辑导致,默认采用SINGLE_NO_PARENT的B3格式,不会写入父span信息。

自定义配置的效果

注册如下Bean覆盖默认传播设置后,链路表现符合预期:

  1. 应用REST控制器内部链路:traceId=T, spanId=S1, parentSpanId=S0
  2. 应用内部触发消息投递前的链路:traceId=T, spanId=S2, parentSpanId=S1
  3. RabbitMQ队列中的消息头:b3=T-S2-1-S1

对应的自定义配置代码:

@Configuration
public class B3Configuration {
  @Bean
  Propagation.Factory customPropagationFactory() {
    return B3Propagation.newFactoryBuilder()
        .injectFormat(B3Propagation.Format.SINGLE)
        .injectFormat(Span.Kind.CLIENT, B3Propagation.Format.SINGLE)
        .injectFormat(Span.Kind.CONSUMER, B3Propagation.Format.SINGLE)
        .injectFormat(Span.Kind.PRODUCER, B3Propagation.Format.SINGLE)
        .build();
  }
}

默认行为的设计逻辑

Hoxton版本的Sleuth基于Brave 5.x的新传播模型,选择SINGLE_NO_PARENT作为生产端的默认格式,是遵循了B3传播规范的通用设计:

  • 异步消息场景下,生产端投递消息时,本质是把当前链路上下文作为下游消费端的根上下文,不需要额外传递父span——因为下游消费端启动新的链路span时,上游生产端的span本身就是链路的边界
  • 旧Greenwich版本的传播逻辑没有遵循这个设计,会把跨进程的父span一并写入消息头,实际会造成下游消费端的链路嵌套深度无意义增加
  • 需要手动修改配置拿到parentSpan的场景,本质是有跨系统全链路串联需求:希望把应用内发消息的上下文span值作为消息头里的父span,方便追踪从发消息逻辑到队列的完整关联。这时候只需要把生产端的B3注入格式从SINGLE_NO_PARENT改成SINGLE格式即可。

更优的实现方式

现有自定义配置存在冗余,不需要给所有Span.Kind都指定SINGLE格式,只需要精准覆盖生产端的注入格式即可,简化后的配置为:

@Configuration
public class SleuthPropagationConfig {
    @Bean
    Propagation.Factory b3PropagationFactory() {
        return B3Propagation.newFactoryBuilder()
                // 仅覆盖生产端的注入格式为带父span的SINGLE格式
                .injectFormat(Span.Kind.PRODUCER, B3Propagation.Format.SINGLE)
                .build();
    }
}

注意:CLIENT、CONSUMER、全局默认格式保留SINGLE_NO_PARENT的默认配置即可兼容跨服务的正常链路追踪,仅修改PRODUCER类型的注入格式,就能拿到消息头带parentSpan的预期效果,不会影响其他场景的传播性能。

内容的提问来源于stack exchange,提问作者wabrit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.13 13:54:04