go http服务需显式配置readtimeout(建议5s)、writetimeout(建议10s)和idletimeout(http/1.1推荐30–60s),禁用defaultservemux,优选chi路由,用sync.pool复用bytes.buffer等对象,并在客户端调用中使用context.withtimeout确保超时可控。

Go HTTP 服务卡顿、goroutine 暴涨、内存持续上升?大概率是没设超时,或者复用方式不对——不是代码写得不够“Go”,而是默认配置根本不能直接上生产。
必须显式配置 ReadTimeout、WriteTimeout 和 IdleTimeout
Go 的 http.Server 默认不设任何超时,一个慢客户端、一次卡住的数据库查询、甚至网络中间件丢包,都会让 goroutine 挂着不退,文件描述符和内存只增不减。
-
ReadTimeout控制从 TCP 连接建立到读完全部请求头+body 的总时间(含 TLS 握手),建议5 * time.Second;超时后连接直接关闭,不进路由逻辑 -
WriteTimeout是从WriteHeader开始到整个响应体写完的上限,包含中间件、DB 查询、模板渲染等所有耗时,建议10 * time.Second;设为 0 或过长(如 30s)极易触发too many open files -
IdleTimeout管理 Keep-Alive 连接空闲期,HTTP/1.1 下推荐30–60 * time.Second;HTTP/2 下影响小但仍建议设,避免客户端异常断连后连接滞留
别依赖 Nginx 的超时——它只管转发,Go 内部仍在等。
禁用 http.DefaultServeMux,改用 chi 或自定义 http.ServeMux
直接调用 http.HandleFunc 会注册到全局 http.DefaultServeMux,它底层是带 sync.RWMutex 的 map,路由匹配靠线性遍历,50+ 路由时延迟就明显上升;还不支持路径参数、中间件链、context 取消传播。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 至少 new 一个独立
http.ServeMux实例,隔离作用域,减少锁争用 - 生产环境优先选
chi:零分配路由匹配、原生context.Context传递、中间件链清晰;gorilla/mux更成熟但稍重;别自己写正则路由——每次请求都regexp.Compile会逃逸+GC - 务必在启动时关掉默认 mux:
http.DefaultServeMux = http.NewServeMux()或更彻底地不碰它,防止意外注册 handler 导致冲突
用 sync.Pool 复用高频临时对象,别每次都 new
每秒上千请求时,反复 new(bytes.Buffer)、json.NewEncoder(nil)、make(map[string]string) 会导致 GC 频繁触发 STW,延迟抖动肉眼可见。
- 适合池化的对象:短生命周期、无外部状态、可安全重置,比如
bytes.Buffer(用前Reset())、json.Encoder(用enc.Reset(w)绑定新 response)、小结构体指针 - 别把已写入数据的
bytes.Buffer直接放回池里;也别往池里塞带 finalizer 或持有资源句柄的对象 - 示例:
var jsonPool = sync.Pool{New: func() interface{} { return json.NewEncoder(nil) }},handler 中enc := jsonPool.Get().(json.Encoder); defer jsonPool.Put(enc)
客户端调用必须带 context.WithTimeout,别只靠 Client.Timeout
在 handler 里用 http.DefaultClient.Get 或未设超时的自定义 client,等于主动放弃控制权。一次 DNS 卡顿、重定向循环或响应头迟迟不来,goroutine 就永远 hang 在那里。
-
Client.Timeout只覆盖整个请求周期(从Do到Body.Read结束),无法中断 DNS 解析或重定向跳转 - 正确做法是用
context.WithTimeout(r.Context(), 2*time.Second)创建子 context,再http.NewRequestWithContext;这样 cancel 会传播到 Transport 层,真正释放连接 - 同时调优
http.Transport:把MaxIdleConnsPerHost提到200,IdleConnTimeout设为90 * time.Second,避免压测时大量dial tcp: lookup错误
最易被忽略的一点:超时值不是拍脑袋定的。它得比下游依赖的 P99 响应时间高一点,但又不能高到掩盖问题——比如 DB 查询设 5s 超时,结果慢查询实际要 8s,那这个超时就失去了意义。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










