使用Spring Cloud Sleuth与Brave时如何保障跨微服务的parentId一致?
链路parentId不一致根因
Brave默认Kafka链路传播机制下,每个Producer发送消息时会把当前服务的活跃span作为父上下文注入Kafka Header,下游Consumer消费时会自动以该父上下文创建新的消费span,该服务后续所有span的parentId都会是这个新消费span的id,因此微服务2的parentId是微服务1的发送span id,微服务3的parentId是微服务2的发送span id,天然存在差异。
全局parentId一致的实现方案
如果需要所有微服务的parentId都统一(通常是统一以链路发起方微服务1的初始span作为全局父span),可按以下步骤操作:
- 关闭消费侧自动生成消费span的默认配置
若使用brave-instrumentation-kafka-clients依赖,构建Consumer时不要使用默认的TracingConsumer包装;若使用Spring Cloud Sleuth整合Brave,可直接添加配置:spring.sleuth.messaging.kafka.consumer.enabled=false - 生产者端新增根链路上下文自定义Header
在链路发起方(微服务1)发送Kafka消息时,额外把全局需要共享的父span的traceId、spanId作为自定义Header注入消息:
后续每个微服务向下游发送消息时,都需要把这两个Header原样传递到新的Kafka消息中。Span rootParentSpan = tracer.currentSpan(); // 注入自定义根上下文Header producerRecord.headers() .add("global-trace-id", rootParentSpan.context().traceIdString().getBytes(StandardCharsets.UTF_8)); producerRecord.headers() .add("global-parent-id", rootParentSpan.context().spanIdString().getBytes(StandardCharsets.UTF_8)); - 消费侧手动绑定全局父上下文
微服务2、微服务3消费消息时,先从Header中读取全局根上下文,手动构建为当前服务所有span的父上下文:// 从Header读取全局根上下文 String globalTraceId = new String(consumerRecord.headers().lastHeader("global-trace-id").value(), StandardCharsets.UTF_8); String globalParentId = new String(consumerRecord.headers().lastHeader("global-parent-id").value(), StandardCharsets.UTF_8); // 构建父TraceContext TraceContext globalParentContext = TraceContext.newBuilder() .traceId(globalTraceId) .spanId(globalParentId) .build(); // 绑定为当前服务活跃span的父 try (Tracer.SpanInScope scope = tracer.withSpanInScope(tracer.newSpan(globalParentContext).name("service-business").start())) { // 执行业务逻辑,该范围内所有子span的parentId都会统一为globalParentId // 向下游发送消息时,原样传递global-trace-id、global-parent-id两个Header即可 } finally { tracer.currentSpan().finish(); }
特殊场景说明:如果微服务1、2、3是消费同一个Kafka Topic的不同消费者组,只需要微服务1发送时注入一次全局根上下文,三个服务分别绑定即可,不需要额外传递Header。
内容的提问来源于stack exchange,提问作者JustLift
相关产品推荐
相关产品推荐

