Go语言并发请求接口怎么做_Golang高并发请求实战

13次阅读

应使用带缓冲的channel或semaphore控制并发数,而非无限制启动goroutine;sync.WaitGroup仅负责等待完成,真正的限速需通过make(chan Struct{}, 10)等信号量机制实现。

Go语言并发请求接口怎么做_Golang高并发请求实战

goroutine + WaitGroup 控制并发数,别直接起成百上千个

Go 启动协程成本低,但不加限制地 go request() 一百次,可能瞬间打爆目标接口或本地文件描述符。关键不是“能不能并发”,而是“怎么安全控速”。sync.WaitGroup 负责等待全部完成,而真正控并发得靠带缓冲的 channelsemaphore

  • make(chan struct{}, 10) 做信号量:每次请求前 sem ,结束后 ,天然限流 10 并发
  • 别把 http.Client 创建放在 goroutine 里——复用一个实例,否则 DNS 缓存、连接池全失效
  • context.WithTimeout 必须设,否则单个失败请求可能卡死整个批次
sem := make(chan struct{}, 5) // 限制 5 并发 var wg sync.WaitGroup for _, url := range urls {     wg.Add(1)     go func(u string) {         defer wg.Done()         sem <- struct{}{}>    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)     defer cancel()      req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)     resp, err := client.Do(req)     if err != nil {         log.Printf("fail %s: %v", u, err)         return     }     resp.Body.Close() }(url)

} wg.Wait()

http.Transport 不调优 = 并发再高也白搭

默认 http.DefaultClient 的 Transport 对并发极其保守:最多 100 个空闲连接、每 host 2 个连接、30 秒 idle timeout。压测时经常看到大量 net/http: request canceled (Client.Timeout exceeded while awaiting headers),其实不是超时,是连接被复用阻塞了。

  • MaxIdleConnsMaxIdleConnsPerHost 至少设到 200+,尤其目标 host 少的时候
  • IdleConnTimeout 建议 30–90 秒,太短导致频繁建连,太长占着连接不放
  • ForceAttemptHTTP2: true(Go 1.12+ 默认开启,但显式写上更安心)
client := &http.Client{     Transport: &http.Transport{         MaxIdleConns:        200,         MaxIdleConnsPerHost: 200,         IdleConnTimeout:     60 * time.Second,         ForceAttemptHTTP2:   true,     }, }

批量请求失败后怎么重试又不雪崩?用带退避的 Backoff

直接 for-loop 重试 3 次?网络抖动时所有请求在同一毫秒重试,流量翻三倍。得用指数退避(exponential backoff),且每个请求独立退避,避免同步重试。

  • 别手写 time.Sleep(time.Second * 1 ——用 github.com/cenkalti/backoff/v4 更稳
  • 只对 5xx 和部分 429 重试,4xx(如 400、401)基本是客户端错,重试无意义
  • 重试次数建议 ≤3,总耗时控制在原始 timeout 内,否则拖垮整批
operation := func() error {     resp, err := client.Do(req)     if err != nil {         return err     }     if resp.StatusCode >= 500 || resp.StatusCode == 429 {         return fmt.Errorf("server error: %d", resp.StatusCode)     }     return nil } 

err := backoff.Retry(operation, backoff.WithContext( backoff.NewExponentialBackOff(), ctx, ))

别忽略 DNS 和 TLS 握手开销——本地缓存和复用很重要

高频请求同一域名时,反复解析 DNS、建立 TLS 连接会吃掉大量时间。Go 默认不缓存 DNS 结果(除非启用了 GODEBUG=http2client=0 这类非标方式),TLS 会话复用也依赖 Transport 配置。

立即学习go语言免费学习笔记(深入)”;

  • net.Resolver 自建内存缓存 DNS,TTL 按实际服务稳定性设(比如 5 分钟)
  • Transport.TLSClientConfig 中启用 SessionTicketsDisabled: false(默认已是 false,但显式确认)
  • 如果目标 API 支持 HTTP/2,确保服务端没禁用 ALPN,否则降级 HTTP/1.1 会损失连接复用能力

并发请求不是堆 go 关键字,是平衡 transport 层、网络层、业务错误处理的系统动作。最容易被跳过的其实是 DNS 缓存和 TLS 复用——它们不报错,但让 QPS 卡在 200 上不去,排查时却总先怀疑代码逻辑。

text=ZqhQzanResources