Trace 上下文在 HTTP、RPC 和 MQ 中如何传播?
简化版
Trace 上下文需要随着请求跨服务传播。HTTP 通常通过 Header,RPC 通过 Metadata 或 Attachment,MQ 通过消息 Header 或属性字段传递 TraceId、SpanId、采样标记等信息。下游收到后继续创建子 Span,才能把链路串起来。
如果上下文没有传播,追踪链路就会断,表现为下游生成了新的 Trace,无法和上游请求关联。异步消息场景尤其容易断链,需要在生产消息和消费消息时都正确处理上下文。
详细版
在同步调用中,上游客户端发起 HTTP 或 RPC 请求前,会把当前 Trace 上下文注入请求元数据。下游服务端从请求中提取上下文,创建自己的服务端 Span,并把它作为上游 Span 的子节点。常见标准包括 W3C Trace Context 的 traceparent、tracestate。
在消息队列中,生产者发送消息时把 Trace 上下文写入消息属性,消费者消费消息时提取上下文并创建消费 Span。因为 MQ 是异步边界,生产 Span 和消费 Span 的关系不一定是简单同步父子关系,还要考虑延迟、重试、批量消费和死信队列。
工程上要确保网关、RPC 框架、HTTP 客户端、MQ 客户端都接入追踪埋点。只在业务代码里生成 TraceId 不够,跨协议传播才是链路完整的关键。
完整版教学
一、上下文传播为什么重要
链路追踪要把多个服务里的 Span 串成一条 Trace。串起来的前提是下游知道自己属于哪个 Trace,以及自己的父 Span 是谁。
如果没有上下文传播,下游服务只能创建一个新的 Trace。结果就是调用链断成两段:上游看不到下游,下游也不知道是谁调用了自己。
因此上下文传播是链路追踪的骨架。
二、HTTP 中如何传播
HTTP 调用最常见的方式是通过 Header。上游在发请求时注入追踪 Header,下游从 Header 中提取。
W3C Trace Context 中常见 Header 是:
traceparent: 00-<trace-id>-<parent-id>-<trace-flags>
tracestate: vendor-specific-data
很多追踪系统也支持 B3、Jaeger 等历史格式。实际工程中要统一传播格式,或者使用兼容多个格式的传播器,避免不同框架之间断链。
三、RPC 中如何传播
RPC 框架通常有 Metadata、Attachment、Invocation Context 等机制。gRPC 使用 Metadata,Dubbo 可以使用 Attachment,其他框架也有类似扩展字段。
拦截器会在客户端调用前注入上下文,在服务端收到请求时提取上下文。这类埋点最好放在框架层,而不是每个业务方法手写,否则容易漏掉。
RPC 传播要注意多语言兼容。如果 Java、Go、Python 服务都在同一链路里,传播字段和语义必须统一。
四、MQ 中如何传播
消息队列是链路追踪最容易断的地方。生产者发送消息时,要把当前 Trace 上下文写入消息 Header 或属性;消费者收到消息时,再从消息中提取上下文。
异步消息和同步调用不同。生产者发送消息后,消费者可能几秒、几分钟甚至更久后处理。消费失败还可能重试、进入死信队列或被批量消费。
因此 MQ 追踪通常会区分发送 Span、Broker 处理、消费 Span,并记录消息 topic、partition、offset、messageId、消费组等信息。
五、批量和异步场景的边界
批量消费时,一个消费操作可能对应多条消息,每条消息可能来自不同 Trace。简单把整个批次归到一个 Trace 可能不准确。系统要么为每条消息分别提取上下文,要么在追踪模型中明确批量处理关系。
异步任务也类似。线程池、定时任务、CompletableFuture、协程切换都会丢失线程本地上下文。如果追踪 SDK 只依赖 ThreadLocal,就要在异步边界做上下文复制和恢复。
六、传播敏感信息要克制
Trace 上下文应该只传播追踪所需的标识和少量控制信息,不应该把用户隐私、Token、密码、完整业务参数塞进上下文。上下文会经过很多服务和中间件,一旦包含敏感信息,泄露面很大。
业务关联字段可以放在 Span 标签或日志里,但要脱敏并控制基数。
七、面试回答建议
回答时可以按协议分开:HTTP 用 Header,RPC 用 Metadata/Attachment,MQ 用消息属性。然后强调下游提取上下文后创建子 Span,异步场景要处理线程切换、批量消费和重试。
最后补一句:上下文传播最好由框架拦截器或 OpenTelemetry 自动埋点完成,避免业务代码手动传递导致遗漏。
八、常见追问和落地边界
常见追问是跨线程为什么会丢 Trace。很多 SDK 把上下文放在 ThreadLocal 中,而线程池、异步回调、协程切换都会换执行上下文。如果没有包装任务或框架支持,上下文不会自动跟过去,日志和 Span 就会断。
还有一个边界是边缘入口。网关、定时任务、消息消费者、批处理任务都可能成为 Trace 根节点。不要只在 HTTP Controller 里建 Trace,否则异步链路和后台任务会缺少入口追踪。
九、常见误区与追问
这道题要紧扣「上下文传播」本身回答,不能把它混成泛泛的可观测性套话。面试官通常会沿着“为什么需要、流程怎么走、失败怎么兜、代价是什么”继续追问,所以回答要覆盖日志、指标、追踪、上下文传播、采样、告警和排障闭环。
| 回答层次 | 要讲清的内容 | 容易漏掉的边界 |
|---|---|---|
| 核心结论 | 上下文传播把 TraceId、SpanId、租户、用户、采样标记等信息跨线程、RPC、消息队列传下去 | 不要停在名词解释 |
| 流程机制 | 入口生成上下文 -> 写入 HTTP/gRPC header -> 服务解析并创建子 Span -> 异步线程显式传递 -> 消息写入 trace 字段 -> 日志和指标关联上下文 | 要说清触发点、状态变化、确认点和失败兜底 |
| 工程取舍 | HTTP 请求进入网关生成 TraceId 后,后续订单、库存、支付服务都要带同一个 TraceId 才能串成链路 | 可观测性不能替代稳定性设计,它用额外采集、存储和分析成本换更快定位问题 |
上下文传播 面试拆解:
1. 入口生成上下文
2. 写入 HTTP/gRPC header
3. 服务解析并创建子 Span
4. 异步线程显式传递
5. 消息写入 trace 字段
6. 日志和指标关联上下文
记忆钩子:先给结论,再拆流程,再讲数字例子和失败边界;回答「上下文传播」时要围绕题目问法收束,不要把相邻概念堆成一段没有重点的名词清单。
- 误区:有 TraceId 就自动完成上下文传播。 跨线程、异步任务和消息队列都可能丢上下文,需要显式传递或框架增强。
- 误区:上下文里可以塞任意业务数据。 上下文应小而稳定,过大会增加网络和日志成本,也有隐私风险。
- 误区:只要 HTTP 传播就够了。 RPC、MQ、定时任务和线程池也要考虑。
- 追问:W3C Trace Context 是什么? 用 traceparent、tracestate 等 header 标准化跨系统追踪上下文。
- 追问:线程池为什么会丢 TraceId? ThreadLocal 不会自动跨线程,需要包装 Runnable 或使用上下文传播库。
- 追问:消息队列如何传播? 把 trace 信息写入消息 header 或属性,消费者创建 consumer span。
十、加强记忆
上下文传播可以记成“接力棒”。HTTP、RPC、MQ 只是不同赛道,核心都是上游把 Trace 信息交给下游。接力棒掉了,链路就断了。
同步调用看 Header 和 Metadata,异步调用重点看消息属性和线程上下文。