为何并发使用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耗时持续变长。
差异原因分析
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数值越高。
- 第一种场景是串行执行:每次循环完成一个请求后才开始下一个,goroutine无需等待调度,
代码变量错误(次要)
第二种场景的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
相关产品推荐
相关产品推荐

