HTTP Header 是跨服务传递 context 的事实标准,因其轻量、可控、易调试,且不污染业务 payload,支持透传如 X-Request-ID 等字段,所有 HTTP 组件原生支持。

为什么 HTTP Header 是跨服务传递 context 的事实标准
Go 的 context.Context 本身不跨网络边界,微服务间必须靠序列化载体重建。HTTP Header 是最轻量、最可控、最易调试的选择——它不污染业务 payload,支持透传(如 X-Request-ID、X-Trace-ID),且所有 HTTP 客户端/服务端都原生支持读写。
关键点:不能把 context.Context 直接塞进 JSON body 或 gRPC metadata(除非你手动提取并重建),否则会丢失 deadline、cancel 信号等核心语义。
- 必须提取需要透传的字段(如
request_id、user_id、trace_id)→ 写入 Header - 下游服务收到请求后,从 Header 构造新的
context.Context(用context.WithValue或更规范的 typed key) - 避免在 Header 中传递敏感信息或大体积数据(如 token 原文、完整 user struct)
如何用 metadata.MD 在 gRPC 中安全透传 context 字段
gRPC 的 metadata.MD 是专为上下文透传设计的机制,比裸用 HTTP Header 更结构化,且天然支持 unary 和 streaming 场景。但要注意:它只在单次 RPC 调用中有效,不会自动跨链路传播(比如 A→B→C,需 B 主动从入参 metadata 中读取、修改、再透传给 C)。
常见错误是直接用 md.Set("key", "value") 写入原始字符串,导致大小写敏感、重复键覆盖、无类型校验等问题。
立即学习“go语言免费学习笔记(深入)”;
- 始终用
metadata.Pairs构建初始 metadata(如metadata.Pairs("x-request-id", reqID, "x-user-id", userID)) - 服务端用
metadata.FromIncomingContext(ctx)获取入参 metadata;客户端用metadata.NewOutgoingContext(ctx, md)注入 outgoing metadata - 不要在 metadata 中存
context.Context本身或闭包,只存可序列化的 string / []string
context.WithValue 的正确用法与典型误用
context.WithValue 不是通用的“存储容器”,它是为传递**请求生命周期内不可变的、与控制流强相关的元数据**而设计的。滥用会导致内存泄漏、类型断言失败、调试困难。
真实项目里最容易踩的坑是:用 string 作 key,造成冲突或拼写错误;或把用户 session、DB 连接等本该由依赖注入管理的对象硬塞进去。
- 必须定义私有、未导出的类型作为 key(如
type ctxKey string; const requestIDKey ctxKey = "req_id") - 只存轻量、只读、跨中间件/拦截器需要共享的数据(如
userID、traceID、authScopes) - 绝不存指针、map、slice、函数等可能被意外修改的值;也不存需要 cleanup 的资源
type ctxKey string
const userIDKey ctxKey = "user_id"
// 正确:传入 string 值
ctx = context.WithValue(ctx, userIDKey, "u_12345")
// 错误:传入 map,后续修改会影响所有持有该 ctx 的 goroutine
ctx = context.WithValue(ctx, userIDKey, map[string]string{"id": "u_12345"})
跨服务 trace ID 透传时,otel.GetTextMapPropagator() 怎么和自定义 header 协同工作
如果你用 OpenTelemetry,otel.GetTextMapPropagator().Inject() 会往 carrier(如 http.Header 或 metadata.MD)里写入 W3C Trace Context 标准字段(traceparent, tracestate)。但它**不会自动处理你的业务 header**(如 X-User-ID),这两者必须分开管理。
容易忽略的是:OpenTelemetry propagator 默认只读写标准字段,不识别或透传自定义 header。你需要显式桥接。
- 在 client 端:先调用
propagator.Inject()写 trace 字段,再手动 set 业务 header(如req.Header.Set("X-User-ID", userID)) - 在 server 端:先用
propagator.Extract()恢复 trace context,再从 header 手动读取业务字段并注入到新 context - 如果用了 gRPC,carrier 类型是
metadata.MD,注意 key 名称要小写(gRPC 自动转小写),例如"x-user-id"而非"X-User-ID"
真正麻烦的不是怎么写,而是所有服务(包括第三方 SDK、中间件、网关)是否都遵循同一套透传规则——漏掉一个环节,trace 就断了,业务字段就丢了。










