context.WithTimeout 是 Go 微服务中最常用且易出错的超时控制方式,需确保每个阻塞操作都接收并传递 ctx,监听 Done() 并主动退出,避免协程泄漏和资源堆积。

context.WithTimeout 是最常用也最容易出错的超时控制方式
Go 微服务中绝大多数 HTTP 请求、gRPC 调用、数据库查询的超时,都该用 context.WithTimeout 包裹。它不是“加个超时参数”那么简单——超时一旦触发,context 会关闭其 Done() channel,所有监听该 context 的 goroutine 必须主动检查并退出,否则协程泄漏、资源不释放、连接堆积都是必然结果。
常见错误包括:
- 只在入口层传入
ctx,但下游调用(如http.Client.Do、sql.DB.QueryContext)没显式使用它 - 在 goroutine 中直接用
context.Background(),绕过了上游传入的超时控制 - 超时时间设得太短(比如 100ms),却没考虑网络抖动或下游链路毛刺,导致大量非业务性失败
正确做法是:每个可能阻塞的操作,都确保接收并传递 ctx;并在关键分支做 select { case 判断。
HTTP Server 端超时必须分层设置,不能只靠 ReadTimeout
http.Server 的 ReadTimeout 和 WriteTimeout 只管 TCP 连接层面的读写,对 handler 内部耗时完全无感。微服务中真正需要控制的是“从收到请求到返回响应”的端到端耗时,这得靠 context + 中间件实现。
立即学习“go语言免费学习笔记(深入)”;
典型结构如下:
func timeoutMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
注意:http.Server.ReadHeaderTimeout 和 IdleTimeout 值得配(防慢速攻击),但它们和业务超时无关;TimeoutHandler 虽然存在,但它会直接返回 503,无法自定义错误响应格式,不适合微服务统一错误处理场景。
gRPC 客户端超时必须绑定到每个 RPC 调用,而非连接级别
gRPC 的 context 超时是 per-RPC 的,不是 per-connection。即使你用同一个 *grpc.ClientConn,每次 Invoke 或 NewStream 都要传入带超时的 ctx。漏掉一次,就等于这次调用永不超时。
常见误区:
- 以为设置了
grpc.WithTimeoutDial 选项就能控制调用超时(实际无效,该选项已废弃) - 复用一个 long-lived
context.Background()创建 client,导致所有 RPC 都不受限 - 在流式 RPC(stream)中只在建立 stream 时设超时,但没对
Recv()/Send()单独加更细粒度的 context 控制
推荐写法:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
数据库查询超时必须用 Context,Driver 层支持差异大
像 database/sql 的 QueryContext、ExecContext 是标准接口,但底层 driver 是否真正中断执行,取决于其实现。MySQL 的 go-sql-driver/mysql 支持 context 中断(需开启 timeout 参数),PostgreSQL 的 lib/pq 也支持,但 SQLite 的 mattn/go-sqlite3 在忙等待场景下可能无法及时响应。
所以不能只信 context,还要配合数据库侧配置:
- MySQL:设置
max_execution_time(单位毫秒),作为 SQL 层兜底 - PostgreSQL:用
statement_timeout(单位毫秒) - 连接池本身也要设
SetConnMaxLifetime和SetMaxIdleConns,避免因连接老化引发的隐式阻塞
真正可靠的超时,是 context 控制 + 数据库 session 级超时 + SQL hint(如 MySQL 的 /*+ MAX_EXECUTION_TIME(3000) */)三层叠加。










