Context 是 Go 服务控制请求生命周期的标准机制。客户端断开、上游超时或业务主动取消时,取消信号应沿调用链传播到数据库查询、远程请求和后台任务,从而快速释放连接与计算资源。

核心原则

  • 把 context.Context 作为函数的第一个参数传入,不要保存在全局变量或长期结构体中。
  • 在边界处创建超时,并始终 defer cancel,及时释放内部计时器资源。
  • 把同一个 Context 传给数据库、HTTP 客户端和子任务,让取消可以级联生效。
  • Context Value 只放请求范围的小型元数据,例如 trace ID,不用于传递可选业务参数。
  • 收到 ctx.Done 后应尽快返回,并通过 errors.Is 判断 DeadlineExceeded 或 Canceled。

推荐的实践步骤

HTTP 处理器通常继承请求自带的 Context,再根据业务 SLA 创建更短的超时。下游调用使用 NewRequestWithContext 或 QueryContext;并发子任务共享派生 Context。当任一关键任务失败时调用 cancel,其他任务即可停止。日志中记录超时阶段和剩余预算,能够帮助定位是哪一层消耗了时间。

func load(ctx context.Context, db *sql.DB, id int64) error {
    ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
    defer cancel()
    row := db.QueryRowContext(ctx,
        "select name from users where id = ?", id)
    return row.Scan(new(string))
}

示例用于说明实现思路,实际项目还应结合所使用的框架版本、部署环境和业务约束进行调整。重要配置要进入版本管理,并在测试环境验证后再发布。

常见误区

  • 使用 context.Background 替换传入的请求 Context,会切断上游取消信号。
  • 创建 WithTimeout 后忘记调用 cancel,会让相关资源一直存活到超时。
  • 把用户对象或数据库连接放进 Context,会模糊依赖关系并增加维护成本。

上线前检查清单

  • 确认“把 context.Context 作为函数的第一个参数传入,不要保存在全局变量或长期结构体中”已经通过代码审查或运行验证。
  • 确认“在边界处创建超时,并始终 defer cancel,及时释放内部计时器资源”已经通过代码审查或运行验证。
  • 确认“把同一个 Context 传给数据库、HTTP 客户端和子任务,让取消可以级联生效”已经通过代码审查或运行验证。
  • 确认“Context Value 只放请求范围的小型元数据,例如 trace ID,不用于传递可选业务参数”已经通过代码审查或运行验证。
  • 为失败路径、边界条件和回滚方案准备测试或演练记录。
  • 上线后观察错误率、延迟和资源消耗,确认变化符合预期。

总结

Go Context 超时、取消与请求链路控制的关键在于把隐含假设变成可执行的约束,并通过测试、监控和复盘持续验证。先从影响最大的真实场景开始,小步调整并保留回滚能力,通常比一次性大范围改造更安全,也更容易积累可复用的工程经验。