Go的http.Server需显式配置超时参数以应对高并发,否则默认禁用的ReadTimeout、WriteTimeout和IdleTimeout会导致goroutine堆积;所有I/O操作必须使用context控制超时;sync.Pool复用小对象可减轻GC压力;应监控goroutine数量与GC频率以防泄漏或性能退化。

Go 的 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状态 - 如果用了反向代理(如 Nginx),还要确保它的
proxy_read_timeout和 Go 的WriteTimeout对齐,否则可能提前断连
别在 handler 里做同步阻塞操作,尤其是数据库和 HTTP 调用
goroutine 轻量不等于无限,一个 handler 里调 db.QueryRow() 或 http.Get() 并等待结果,这个 goroutine 就卡住了,无法调度。并发量上来后,积压的 goroutine 会吃光内存,触发 GC 频繁或 OOM。
正确做法是把耗时操作拆出来,必要时加超时和熔断:
立即学习“go语言免费学习笔记(深入)”;
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})}
- 所有 I/O 操作(DB、HTTP client、cache)必须用带
context.Context的版本,比如QueryRowContext、DoContext - 不要用
time.Sleep做“限流”或“降级”,它会直接阻塞 goroutine;改用time.AfterFunc或异步任务队列 - 第三方 HTTP 调用务必设超时,
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 的内存分配器对大对象走的是不同路径,复用收益低 - Pool 中的对象可能被 GC 清理掉,所以每次
Get()后必须检查并重置状态(比如enc.Reset(w)) - 别试图用 Pool 管理有外部依赖的对象(如 DB 连接、文件句柄),它们的生命周期必须由更严格的机制控制
别迷信“开更多 goroutine”,要监控 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)
})- goroutine 数长期高于 5000 就该警觉,尤其稳定流量下还在缓慢上涨,大概率有泄漏(比如 channel 未关闭、timer 未 stop)
- 用
pprof抓取/debug/pprof/goroutine?debug=2查看堆栈,重点关注卡在chan receive或select的 goroutine - GC 次数每秒超过 5 次,说明分配压力过大,优先查
sync.Pool是否没用好、是否有大量小对象逃逸到堆上
高并发不是堆资源换来的,是靠收敛阻塞点、控制资源生命周期、让每个 goroutine 尽快完成并退出。最危险的优化,就是以为“Go 自带高并发”就不再看 runtime 行为。










