context传递需显式透传,漏传则下游收不到;key必须用未导出空结构体(如type userIDKey struct{}),禁用字符串避免冲突;HTTP中间件须r=r.WithContext(newCtx)绑定,DB调用用QueryContext(ctx, ...),派生ctx后务必defer cancel()。

Context 传递不是“传一次就完事”,漏传、错传、不更新,下游就收不到信号——超时和取消必然失效。
context.WithValue 的 key 必须用未导出空结构体
用 string 当 key 是最常见也最危险的写法。两个不同包都写 ctx.Value("user_id"),值会互相覆盖,且编译期完全无法发现。
- 正确做法是定义私有类型:
type userIDKey struct{},哪怕只在当前文件用一次 - 存值:
ctx = context.WithValue(ctx, userIDKey{}, userID) - 取值必须用双返回值断言:
if v, ok := ctx.Value(userIDKey{}).(int64); ok { ... } - IDE 点击能跳转到定义;类型不匹配直接编译报错;不同包定义同名 struct 也不会冲突
HTTP handler 到 DB 查询必须显式透传新 ctx
handler 接收的 r.Context() 是只读副本,改了它不会影响后续中间件或 DB 调用。
- 中间件里要更新 context,必须调用
r = r.WithContext(newCtx)才能让下游拿到 - DB 查询务必用
db.QueryContext(ctx, ...),而不是db.Query(...) - 若需额外限制 DB 超时(如最多等 2 秒),用
context.WithTimeout(ctx, 2*time.Second)派生后再传 - 绝对不要把
ctx缓存进 struct 字段——goroutine 泄漏高发区
跨 HTTP 服务调用时超时不会自动透传
context.WithTimeout 只控制本地 goroutine,req.Header 不会自动带上超时信息,下游压根不知道该什么时候停。
- 上游发起请求前,手动设置 header:
req.Header.Set("X-Request-Timeout", "2000") - 下游解析该 header,并用它创建自己的
context.WithTimeout - 别依赖
req.Context().Deadline()—— 它只反映本机连接生命周期,不是业务逻辑超时 - 如果用 gRPC,记得启用
grpc.WaitForReady(false)避免阻塞重试
最容易被忽略的是:所有派生 context(尤其是 WithTimeout 和 WithCancel)都必须配 defer cancel(),否则内部 timer 不会被回收,长期运行的服务内存和 goroutine 数会缓慢上涨。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











