goroutine 过多会压垮服务端或触发限流;因 http.transport 默认 maxidleconnsperhost=2,大量并发请求仅2个能发出,其余排队,还可能被目标端拦截或返回429/503;应显式配置 transport 并用带缓冲 channel 控制并发。

goroutine 启动太多会直接压垮服务端或触发限流
Go 的 goroutine 轻量,但不等于“无限开”。HTTP 客户端底层复用连接(http.Transport),而默认的 MaxIdleConnsPerHost 是 2——意味着即使开了 100 个 goroutine 并发请求同一域名,真正能并发发出的可能只有 2 个,其余全在排队等空闲连接。更糟的是,若目标服务没做限流,大量并发请求可能直接触发对方防火墙拦截或 429/503。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 显式配置
http.Transport,例如设MaxIdleConnsPerHost: 100(需配合服务端承受能力评估) - 用带缓冲的 channel 控制并发数,比如
sem := make(chan struct{}, 10),每次请求前sem ,结束后 <code> - 避免对单个域名无节制并发;如需批量调用,优先确认是否支持批量接口(如
/batch)
不加超时控制的 goroutine 很容易永久阻塞
常见错误是写 go http.Get(url) 就完事,没设超时。DNS 解析失败、TCP 握手卡住、服务端迟迟不返回响应体……都会让该 goroutine 永久挂起,内存和 goroutine 数持续增长,最终 OOM。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 永远用
http.Client替代裸http.Get,并设置Timeout、KeepAlive和IdleConnTimeout - 对关键请求,额外加 context 控制生命周期:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second),再传给client.Do(req.WithContext(ctx)) - 注意:
context.WithTimeout不会中断正在读响应体的连接,如需更精细控制,考虑context.WithDeadline或自定义RoundTripper
WaitGroup + goroutine 组合容易漏掉 Done() 导致主协程死锁
典型模式是用 sync.WaitGroup 等待所有请求完成,但常因 panic、提前 return 或 error 分支忘记调用 wg.Done(),导致 wg.Wait() 永远不返回。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把
wg.Add(1)放在 goroutine 外,defer wg.Done()放在 goroutine 内最开头(哪怕后续 panic 也能执行) - 更稳妥的做法是改用
errgroup.Group(来自golang.org/x/sync/errgroup),它自动处理 panic、错误传播和等待逻辑 - 示例:
g, ctx := errgroup.WithContext(ctx) for _, url := range urls { url := url // 避免循环变量捕获 g.Go(func() error { resp, err := client.Get(url) if err != nil { return err } defer resp.Body.Close() return nil }) } if err := g.Wait(); err != nil { // 处理第一个错误 }
HTTP/2 和连接复用对并发性能影响比 goroutine 数量更大
很多人以为“开更多 goroutine = 更快”,其实瓶颈常在 TCP 连接建立、TLS 握手、HTTP 头解析等环节。HTTP/2 多路复用可显著减少连接数和延迟,但需要服务端支持且客户端启用(Go 1.6+ 默认开启,前提是 TLS 且服务端协商成功)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
curl -v --http2 https://example.com或 Wireshark 确认实际走的是 HTTP/2 - 检查服务端是否返回
Alt-Svc头或支持 ALPN 协商;若不支持,强行设ForceAttemptHTTP2: true会失败 - 对高并发短请求场景,启用
http.Transport的MaxConnsPerHost(注意不是MaxIdleConnsPerHost)可提升吞吐,但需权衡端口耗尽风险
实际压测中,往往调优 http.Transport 参数 + 控制并发数 + 使用 errgroup,比盲目增加 goroutine 数量带来的收益高一个数量级。最容易被忽略的是连接池参数与业务请求特征的匹配——比如长轮询场景要调大 IdleConnTimeout,而短平快批量请求则要提高 MaxIdleConnsPerHost 并缩短 ResponseHeaderTimeout。











