context.WithTimeout 不能跨 HTTP 边界自动传播,因其是内存内 Go 对象,需手动通过 X-Timeout-Ms 头传递剩余超时时间,并在服务端解析验证后重建 context;gRPC 则通过 metadata 自动携带 Deadline,但仍需透传 ctx 并使用 Context 感知的 DB 方法。

context.WithTimeout 为什么不能直接跨 HTTP 边界传播
因为 context.Context 是内存中的 Go 运行时对象,HTTP 请求之间没有共享内存,服务 A 创建的 ctx 不可能“自动”变成服务 B 的 ctx。必须手动把超时意图编码进请求头(如 Timeout-Ms),再在服务 B 解析并重建本地 context。
常见错误是只在调用方用 context.WithTimeout,却没在被调用方读取并应用——结果下游服务完全不感知上游超时,还在慢查询里跑着。
- 服务 A 发起 HTTP 调用前,从原始
ctx读取Deadline(),算出剩余毫秒数,写入X-Timeout-Ms头 - 服务 B 的中间件需检查该 header,若存在且为正整数,则用
context.WithTimeout(context.Background(), time.Duration(ms)*time.Millisecond)构建新ctx - 注意:若服务 B 本身也作为客户端调用 C,应沿用这个新
ctx,而非原始或新建无 timeout 的ctx
如何安全地从 HTTP header 提取并验证 timeout 值
直接 strconv.ParseInt(r.Header.Get("X-Timeout-Ms"), 10, 64) 很危险:header 可能为空、含非数字字符、或数值过大(比如传了 9223372036854775807 导致 time.Duration 溢出 panic)。
- 先用
strings.TrimSpace清理空格 - 用
strconv.ParseUint(不是ParseInt)避免负数;上限设为 300000(5 分钟),超过则忽略或返回 400 - 转换后强制限制最大值:
if ms > 300000 { ms = 300000 },再转time.Duration(ms) * time.Millisecond - 若解析失败,建议 fallback 到默认超时(如
context.WithTimeout(ctx, 30*time.Second)),而不是用context.Background()
gRPC 场景下更省事:metadata 自动携带 Deadline
gRPC 的 context 能通过 metadata.MD 自动序列化 Deadline 和取消信号——只要客户端用 context.WithTimeout 或 context.WithCancel,服务端的 ctx 就会自动继承对应 deadline 和 cancel 行为。
但要注意两个坑:
- 服务端 handler 必须用传入的
ctx(即func(ctx context.Context, req *pb.Req)中的ctx),而不是自己 newcontext.Background() - 如果服务端内部又调用其他 gRPC 接口,要透传这个
ctx,别漏掉;若需附加 metadata,用metadata.AppendToOutgoingContext(ctx, ...),别新建 context - 客户端调用时若用
grpc.EmptyCallOption或忽略ctx参数,deadline 就断了
cancel 信号丢失的典型链路:数据库查询 + goroutine
即使 HTTP/gRPC 层正确传递了 ctx,下游操作仍可能无视它。最常见的是启动 goroutine 执行 DB 查询,却没把 ctx 传进去,或者用了不支持 context 的旧版 driver(如老版本 database/sql 驱动未适配 QueryContext)。
- 所有 DB 调用必须用
db.QueryContext(ctx, ...)、tx.StmtContext(ctx, ...)等带Context的方法 - 启动 goroutine 时,显式传入
ctx,并在 goroutine 内部监听ctx.Done(),及时 return - 避免在 goroutine 中用
select {}或无条件 sleep——这会让 goroutine 卡住,无法响应 cancel
跨服务的超时控制,本质是每一跳都主动“接棒”,而不是指望某一层自动完成。最容易被忽略的,是中间层(比如网关或中间件)没做 header 解析,或者 DB 层用了不带 Context 的 API。


















