Go微服务高并发下真正的瓶颈是失控goroutine和无deadline连接;必须在Accept后立即动态设置Read/WriteDeadline,用os.IsTimeout判断超时,避免io.Copy导致goroutine泄漏。

Go 微服务在真实高并发场景下,I/O 不是瓶颈,失控的 goroutine 和未设 deadline 的连接才是。
conn.SetReadDeadline 必须在每次 Accept 后立刻设置
HTTP/1.1 keep-alive 下,http.Server.ReadTimeout 只管请求头,body 读取阶段完全不生效。一旦客户端慢速上传或中途断连,conn.Read() 就会永久阻塞,goroutine 卡死不释放。
- 必须在
accept返回的net.Conn上立即调用conn.SetReadDeadline()和conn.SetWriteDeadline() - deadline 值要用
time.Now().Add(30 * time.Second)动态计算,不能复用旧时间变量 - 错误判断用
os.IsTimeout(err),不是strings.Contains(err.Error(), "timeout") - HTTP handler 内不要再反复调用
SetReadDeadline—— 它对整个连接生命周期有效,重复设置无意义还可能覆盖前值
io.Copy 不感知 context,是 goroutine 泄漏高发区
常见于代理、文件上传、日志转发等场景:你传了 ctx 给 handler,但 io.Copy(dst, src) 内部根本不检查 ctx.Done(),只要连接没关、src 没 EOF,它就一直等。
- 替代方案:用
io.CopyN分块 + 手动 select 检查ctx.Done(),或改用http.TimeoutHandler包裹整个 handler - 更稳妥的做法是自己实现带 context 的 copy:在每次
Read或Write前加select { case - 务必在 goroutine 退出前调用
cancel(),否则 context 树内存泄漏,延迟毛刺会随时间推移越来越明显
缓冲区和连接池不复用,GC 就会周期性卡顿
每秒几百个连接时,bufio.NewReader(conn) 频繁 new 会导致 GC 尖峰;默认 http.Client 连接复用率低,大量 TIME_WAIT 和 TLS 握手开销拖慢吞吐。
立即学习“go语言免费学习笔记(深入)”;
-
bufio.Reader和bufio.Writer必须用sync.Pool复用:New函数返回已初始化实例,如return bufio.NewReaderSize(nil, 4096),每次从 Pool 获取后调用reader.Reset(conn) -
http.Transport必须自定义:设MaxIdleConns、MaxIdleConnsPerHost(建议 ≥ 100),IdleConnTimeout(建议 30s),并启用ForceAttemptHTTP2 - 全局复用一个
http.Client实例,不要每个请求 new 一个
并发数不限制,文件描述符和内存就先爆
启动上万个 goroutine 发 HTTP 请求,不是“快”,而是把本地资源和远端服务一起拖垮。系统报错常是 too many open files 或 context deadline exceeded,但根因是未控并发。
- 用带缓冲 channel 当信号量,例如
sem := make(chan struct{}, 10)控制最大并发为 10 - 优先用
errgroup.Group替代裸sync.WaitGroup:支持 context 取消、自动收集错误、天然限流 - 流式处理大响应体:别用
io.ReadAll(resp.Body),改用json.NewDecoder(resp.Body)或bufio.Scanner边读边解析
真正难的不是写 go f(),而是给每个 I/O 调用配上 deadline、让每个 goroutine 有明确退出路径、把缓冲区和连接当成可复用的资源来管理——这些细节不落地,QPS 上去后崩的不是代码,是整条链路的稳定性。


















