You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何并发使用fasthttp.Client调用API时性能变慢?如何调优?

场景性能差异分析与调优方案

场景代码

第一种场景

var fastClient fasthttp.Client

fastClient = fasthttp.Client{
    ReadTimeout:     500 * time.Millisecond,
    MaxConnsPerHost: 500,
}

runtime.GOMAXPROCS(1)

for i := 0; i < 500; i++ {
    req := fasthttp.AcquireRequest()
    req.Header.SetMethod("GET")
    req.SetRequestURI(TEST_REST_API)
    res := fasthttp.AcquireResponse()
    
    start := time.Now()
    err := client.Do(req, res)
    ms := time.Since(start).Milliseconds()

    fasthttp.ReleaseRequest(req)
    fasthttp.ReleaseResponse(res)

    fmt.Printf("%v %v", ms, string(res.Header.Peek("x-real-service-time")))
}

第二种场景

var fastClient fasthttp.Client

fastClient = fasthttp.Client{
    ReadTimeout:     500 * time.Millisecond,
    MaxConnsPerHost: 500,
}

runtime.GOMAXPROCS(1)

for i := 0; i < 500; i++ {
    go func(id int, c *fasthttp.Client) {
       req := c.AcquireRequest()
       req.Header.SetMethod("GET")
       req.SetRequestURI(TEST_REST_API)
       res := c.AcquireResponse()

       start := time.Now()
       err := client.Do(req, res) // 注:此处存在变量错误,应为c.Do(req, res)
       ms := time.Since(start).Milliseconds()

       c.ReleaseRequest(req)
       c.ReleaseResponse(res)

       fmt.Printf("%v %v %v", id, ms, string(res.Header.Peek("x-real-service-time")))
    }(i, &fastClient)

    // waiting for end all goroutine
}

测试结果与现象

x-real-service-time:服务器实际处理并响应请求的耗时(毫秒)
ms:客户端从发送请求到接收响应的总耗时(毫秒)
id:循环迭代次数(变量i)

第一种场景结果

id: 166 | ms: 115 | x-real-service-time : 105
id: 167 | ms: 103 | x-real-service-time : 97
id: 168 | ms: 89 | x-real-service-time : 73
id: 169 | ms: 92 | x-real-service-time : 76
id: 170 | ms: 79 | x-real-service-time : 73
id: 171 | ms: 81 | x-real-service-time : 73
id: 172 | ms: 84 | x-real-service-time : 76
id: 173 | ms: 84 | x-real-service-time : 78
id: 174 | ms: 81 | x-real-service-time : 73
id: 175 | ms: 82 | x-real-service-time : 76
...

现象:ms与x-real-service-time数值几乎一致。

第二种场景结果

id: 486 | ms: 516 | x-real-service-time : 72
id: 361 | ms: 620 | x-real-service-time : 100
id: 349 | ms: 620 | x-real-service-time : 96
id: 417 | ms: 621 | x-real-service-time : 100
...
id: 267 | ms: 783 | x-real-service-time : 75
id: 195 | ms: 779 | x-real-service-time : 73
...
id: 481 | ms: 825 | x-real-service-time : 67
id: 105 | ms: 823 | x-real-service-time : 75
...
id: 323 | ms: 1019 | x-real-service-time : 201
id: 211 | ms: 1015 | x-real-service-time : 209
...

现象:ms远大于x-real-service-time,且随着id递增,ms耗时持续变长。

差异原因分析

  1. GOMAXPROCS限制导致goroutine调度阻塞
    两种场景都设置了runtime.GOMAXPROCS(1),即整个程序只能使用1个OS线程执行Go代码。

    • 第一种场景是串行执行:每次循环完成一个请求后才开始下一个,goroutine无需等待调度,ms仅包含网络传输+服务器处理时间,因此和x-real-service-time接近。
    • 第二种场景启动了500个goroutine:所有goroutine都要抢占唯一的OS线程执行。由于fasthttp.Client.Do是阻塞调用,每个goroutine执行到Do时会阻塞等待响应,此时Go调度器会切换其他goroutine,但OS线程只有1个,大量goroutine会处于排队等待调度的状态。ms包含了goroutine排队等待调度的时间+网络传输+服务器处理时间,最终导致ms远大于x-real-service-time,且越晚启动的goroutine等待时间越长,ms数值越高。
  2. 代码变量错误(次要)
    第二种场景的goroutine中使用了外部变量client而非传入的c *fasthttp.Client,虽然不影响逻辑,但属于代码错误,可能引发意外问题。

调优方案

方案1:调整GOMAXPROCS为CPU核心数

取消单线程限制,让Go调度器能利用多核CPU并行执行goroutine,减少调度等待时间:

// 替换原runtime.GOMAXPROCS(1)
runtime.GOMAXPROCS(runtime.NumCPU())

方案2:控制goroutine并发数

使用带缓冲的channel作为信号量,限制同时运行的goroutine数量(建议和MaxConnsPerHost保持一致,避免超出连接池限制):

var fastClient fasthttp.Client

fastClient = fasthttp.Client{
    ReadTimeout:     500 * time.Millisecond,
    MaxConnsPerHost: 500,
}

// 设置并发控制信号量,缓冲大小等于MaxConnsPerHost
sem := make(chan struct{}, 500)
// 等待所有goroutine完成的WaitGroup
var wg sync.WaitGroup

runtime.GOMAXPROCS(runtime.NumCPU())

for i := 0; i < 500; i++ {
    wg.Add(1)
    sem <- struct{}{} // 获取信号量,满则阻塞
    go func(id int, c *fasthttp.Client) {
       defer func() {
           <-sem // 释放信号量
           wg.Done()
       }()
       
       req := c.AcquireRequest()
       defer c.ReleaseRequest(req)
       res := c.AcquireResponse()
       defer c.ReleaseResponse(res)

       req.Header.SetMethod("GET")
       req.SetRequestURI(TEST_REST_API)

       start := time.Now()
       err := c.Do(req, res) // 修正变量错误
       ms := time.Since(start).Milliseconds()

       fmt.Printf("%v %v %v", id, ms, string(res.Header.Peek("x-real-service-time")))
    }(i, &fastClient)
}

wg.Wait() // 等待所有goroutine执行完成

方案3:修正代码变量错误

将第二种场景中的err := client.Do(req, res)改为err := c.Do(req, res),确保使用传入的客户端实例。

通过以上调优,第二种场景的ms将仅包含网络传输和服务器处理时间,与x-real-service-time的差值会保持在较小范围。

内容的提问来源于stack exchange,提问作者SungHo Kim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 14:00:53