HTTP服务需设ReadTimeout和WriteTimeout防慢连接拖垮goroutine调度;数据库等阻塞操作须用context控制;复用http.Client并配置Transport参数;合理使用sync.Pool缓存小对象。

用 http.Server 的 ReadTimeout 和 WriteTimeout 防止慢连接拖垮服务
超时设置不是锦上添花,而是防止一个慢请求卡住整个 goroutine 调度队列的关键防线。默认不设超时,遇到网络抖动或恶意长连接,net/http 会一直等下去,堆积大量阻塞 goroutine,最终耗尽内存或触发调度延迟。
-
ReadTimeout控制从连接建立到读完 request header + body 的总时间(注意:body 若用io.Copy流式读取,超时从读开始算) -
WriteTimeout从 response.WriteHeader 调用后开始计时,覆盖 write body 全过程 - 别只设
IdleTimeout—— 它只管空闲时间,对正在传输的大文件或慢客户端无效 - 示例:
srv := &http.Server{ Addr: ":8080", ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, Handler: myHandler, }
避免在 HTTP handler 中做同步阻塞操作
Go 的并发模型依赖于非阻塞 I/O,但很多常见操作实际是同步阻塞的:数据库查询、本地文件读写、调用未加 context 控制的第三方 SDK、甚至 time.Sleep。这些都会让当前 goroutine 挂起,无法被调度器复用。
- 数据库访问必须使用带
context.Context的方法,如db.QueryRowContext(ctx, ...),并在 handler 入口传入带超时的 ctx - 不要用
os.ReadFile读配置或模板——它无超时、无 cancel 支持;改用os.Open+io.ReadFull并配合ctx.Done()检查 - 日志写入若用同步 file writer(如
log.SetOutput直接设 *os.File),高并发下会成为瓶颈;优先用异步封装(如zerolog.ConsoleWriter配合 channel)
合理复用 http.Client 和底层连接
每次 new 一个 http.Client 并发发起请求,不仅浪费内存,还会频繁建连、TLS 握手,显著拉高 P99 延迟。连接复用失效的常见原因是没配好 Transport。
- 全局复用单个
*http.Client实例,不要在 handler 里 new - 必须自定义
http.Transport:设置MaxIdleConns(默认 0)、MaxIdleConnsPerHost(默认 2)、IdleConnTimeout(建议 30s) - 若调用的是同一域名下的多个 API,把
ForceAttemptHTTP2设为 true 可启用连接多路复用 - 错误示范:
http.Get(url)—— 它用的是默认 client,transport 参数全为零值,连接几乎不复用
用 sync.Pool 缓存高频小对象,但别滥用
频繁分配小对象(如 JSON 解析用的 bytes.Buffer、自定义 request struct、临时切片)会加剧 GC 压力,导致 STW 时间变长,间接拖慢响应。但 sync.Pool 不是银弹 —— 错误使用反而增加逃逸和锁开销。
立即学习“go语言免费学习笔记(深入)”;
- 适合缓存:生命周期短、结构稳定、大小可预期的对象(如
json.Decoder、bytes.Buffer) - 不适合缓存:含指针字段过多、内部有 map/slice 且容量波动大、或需要深度初始化的对象
- 务必重置对象状态:从 Pool 获取后必须清空字段(如
buf.Reset()),否则残留数据会导致逻辑错误 - 别在 defer 里 Put —— handler 返回快,但 goroutine 可能被复用,defer 会延迟释放,池内对象老化
真正影响响应速度的,往往不是算法复杂度,而是连接管理是否健壮、上下文是否贯穿全程、对象生命周期是否可控。这些点一旦漏掉一个,压测时 P99 就可能突然跳升几十毫秒,而且很难归因。










