Go高并发核心是控量、防耗尽、准错误处理与超时管理,需用context限超时、semaphore限并发、http.Transport调优连接池,并结构化日志追踪异常。

Go 语言原生支持高并发网络请求,核心不是“怎么加 goroutine”,而是“如何控制并发量、避免资源耗尽、正确处理错误和超时”。盲目起成百上千个 go http.Get() 很容易打垮目标服务或本地文件描述符耗尽。
用 sync.WaitGroup + goroutine 控制基础并发结构
这是最直接的并发模型,但必须配上限流和错误处理,否则只是把问题延迟暴露。
常见错误现象:too many open files(Linux 默认 ulimit -n 1024)、大量 context.DeadlineExceeded 或连接拒绝。
- 始终用
context.WithTimeout()或context.WithCancel()包裹请求,绝不依赖http.Client.Timeout单一设置 -
sync.WaitGroup的Add()必须在 goroutine 启动前调用,不能放 inside goroutine - 每个 goroutine 内部必须显式
defer wg.Done(),且确保无论成功失败都执行 - 示例片段:
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
// ... 处理 resp/err
}(url)
}
wg.Wait()
用 semaphore(信号量)限制并发数,避免压垮服务
无限制并发等于拒绝服务攻击。生产环境必须硬性限流,推荐用 golang.org/x/sync/semaphore。
立即学习“go语言免费学习笔记(深入)”;
使用场景:爬虫、批量健康检查、第三方 API 批量调用(如每秒最多 10 请求)。
-
semaphore.Weighted比自己用chan struct{}更安全,支持带上下文的Acquire() - 注意:
Acquire()可能阻塞或返回 error(如 context canceled),必须检查 - 务必在 goroutine 结束时调用
Release(1),建议用defer sem.Release(1) - 不要把
sem.Acquire()放在循环外——那会变成串行;也不要在每次请求后不释放——导致后续 goroutine 永远拿不到许可
用 http.Transport 调优连接复用与超时
默认的 http.DefaultTransport 不适合高并发:它对同一 host 最多复用 2 个空闲连接,且不设空闲连接超时,容易堆积。
性能影响:未调优时,QPS 可能卡在 20–50;调优后轻松破千(取决于下游能力)。
- 关键参数:
MaxIdleConns(全局最大空闲连接数)、MaxIdleConnsPerHost(单 host 最大空闲连接)、IdleConnTimeout(空闲连接存活时间) - 示例配置:
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
client := &http.Client{Transport: transport}
- 如果目标是 HTTPS,记得加
TLSHandshakeTimeout,否则 TLS 握手慢会导致整个连接池卡住 - 不要复用同一个
*http.Client实例跨不同超时策略的场景(比如有的要 2s,有的要 30s)——应按需构造 client
错误处理必须区分类型,不能只看 err != nil
网络请求失败原因多样,统一重试或忽略会掩盖真实问题。
容易踩的坑:把 net.OpError(如 DNS 失败、连接拒绝)和 context.DeadlineExceeded 当成同一类错误处理,导致重试雪崩。
- 用
errors.As()判断具体错误类型:
if errors.Is(err, context.DeadlineExceeded) {
// 超时,可能需降级或告警
} else if opErr, ok := err.(*net.OpError); ok && opErr.Err != nil {
if strings.Contains(opErr.Err.Error(), "timeout") {
// 底层 IO timeout,非 context 超时
}
}
- 对
429 Too Many Requests和503 Service Unavailable响应码,应主动 sleep 后退避,而不是立即重试 - HTTP 状态码
4xx大多是客户端问题,重试无意义;5xx才考虑有限重试(建议配合指数退避)
真正难的不是并发数量,而是当 1000 个请求里有 3 个失败、2 个超时、1 个返回了异常 body 时,你怎么定位哪一个是 DNS 解析失败、哪一个是对方 TLS 证书过期、哪一个是你的 json.Unmarshal panic 了——日志结构化、错误链路追踪、独立监控指标,这些比 goroutine 数量重要得多。










