Go的http.Server需显式配置超时参数以应对高并发,否则默认禁用的ReadTimeout、WriteTimeout和IdleTimeout会导致goroutine堆积;所有I/O操作必须使用context控制超时;sync.Pool复用小对象可减轻GC压力;应监控goroutine数量与GC频率以防泄漏或性能退化。
http.Server 默认就能扛住高并发,但得关掉默认超时Go 的 HTTP 服务天生基于 goroutine,每个请求启动一个轻量协程,不是像 Node.js 那样单线程轮询,也不是 Java 那样靠线程池硬撑。但很多人一上线就遇到大量 503 Service Unavailable 或连接被重置,问题往往出在 http.Server 的默认超时设置上——ReadTimeout、WriteTimeout、IdleTimeout 全是 0(即禁用),看似安全,实则埋雷。
真实生产环境必须显式配置,否则长连接堆积、慢客户端拖垮整个服务很常见:
server := &http.Server{
Addr: ":8080",
Handler: myRouter,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second, // 关键:防止 TCP 连接空闲太久占着 goroutine
}ReadTimeout 从连接建立开始计时,包括 TLS 握手和请求头读取;太短会误杀 HTTPS 请求IdleTimeout 是关键,它控制 keep-alive 连接的最大空闲时间,不设会导致大量 goroutine 卡在 readRequest 状态proxy_read_timeout 和 Go 的 WriteTimeout 对齐,否则可能提前断连goroutine 轻量不等于无限,一个 handler 里调 db.QueryRow() 或 http.Get() 并等待结果,这个 goroutine 就卡住了,无法调度。并发量上来后,积压的 goroutine 会吃光内存,触发 GC 频繁或 OOM。
正确做法是把耗时操作拆出来,必要时加超时和熔断:
func userHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond) defer cancel()
// 使用带 context 的 DB 查询,避免死等 row := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", userID) var name string if err := row.Scan(&name); err != nil { if errors.Is(err, context.DeadlineExceeded) { http.Error(w, "timeout", http.StatusGatewayTimeout) return } http.Error(w, "db error", http.StatusInternalServerError) return } json.NewEncoder(w).Encode(map[string]string{"name": name})}
context.Context 的版本,比如 QueryRowContext、DoContext
time.Sleep 做“限流”或“降级”,它会直接阻塞 goroutine;改用 time.AfterFunc 或异步任务队列http.DefaultClient 的 Transport 默认没有限制,容易拖垮整个服务sync.Pool 复用高频小对象,减少 GC 压力QPS 上万后,每秒创建成千上万个 bytes.Buffer、json.Encoder 或临时切片,GC 会频繁触发 STW(Stop-The-World),延迟毛刺明显。这时候 sync.Pool 就不是“锦上添花”,而是刚需。
典型场景是 JSON 序列化:
var jsonPool = sync.Pool{
New: func() interface{} {
return json.NewEncoder(nil)
},
}
func encodeJSON(w http.ResponseWriter, v interface{}) {
enc := jsonPool.Get().(*json.Encoder)
defer jsonPool.Put(enc)
w.Header().Set("Content-Type", "application/json")
enc.Reset(w)
enc.Encode(v)}
sync.Pool 不适合存大对象(如 > 2KB 的结构体),因为 Go 的内存分配器对大对象走的是不同路径,复用收益低Get() 后必须检查并重置状态(比如 enc.Reset(w))runtime.NumGoroutine() 和 GC 指标看到 QPS 上不去,第一反应不是加 go f(),而是看当前 goroutine 数和 GC 频率。线上服务突然变慢,90% 是因为 goroutine 泄漏或 GC 压力过大。
加个简单健康端点实时观察:
http.HandleFunc("/debug/goroutines", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "goroutines: %d\n", runtime.NumGoroutine())
fmt.Fprintf(w, "gc count: %d\n", debug.GCStats{}.NumGC)
})pprof 抓取 /debug/pprof/goroutine?debug=2 查看堆栈,重点关注卡在 chan receive 或 select 的 goroutinesync.Pool 是否没用好、是否有大量小对象逃逸到堆上高并发不是堆资源换来的,是靠收敛阻塞点、控制资源生命周期、让每个 goroutine 尽快完成并退出。最危险的优化,就是以为“Go 自带高并发”就不再看 runtime 行为。