前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解 Go HTTP 客户端配置:从 Time Wait 问题到解决方案

深入理解 Go HTTP 客户端配置:从 Time Wait 问题到解决方案

作者头像
超级大猪
发布2024-01-19 14:33:18
2090
发布2024-01-19 14:33:18
举报
文章被收录于专栏:大猪的笔记大猪的笔记

省流内容提要:

  1. 长连接出现大量的 TIME_WAIT 状态,这通常是由于 HTTP 中的 transport 配置 MaxIdleConnsPerHost/MaxIdleConns 设置不当所导致的。次要原因是,go必须将连接中的数据使用io.ReadAll读完才能Close,否则连接也不会复用。
  2. http client中需要传入transport,其中有配置MaxIdleConnsPerHost/MaxIdleConns,这些配置非常重要,在大吞吐的客户端上可以理解为客户端维持的最终连接数。MaxIdleConnsPerHost默认=2,可能太保守。
  3. MaxConnsPerHost一般配置为等于或略大于MaxIdleConnsPerHost,这个值如果配置的过小,在连接达到阈值,会阻塞连接的创建进行等待。从而影响网络的吞吐。如果 MaxConnsPerHost 配置得过大,而 MaxIdleConnsPerHost 配置得过小,则会引发大量的连接创建和销毁造成TIME_WAIT

最近,我在项目中发现查询 InfluxDB 的模块出现了大量的 TIME_WAIT 状态。

项目的架构如下:

代码语言:javascript
复制
用户查询 -> [APISvr] --http post--> influxdb_ip:port

这个服务的主要功能是高频率地向外部提供数据查询,APISvr 每秒对 InfluxDB 发起上万次 HTTP POST 请求。大量的 TIME_WAIT 状态的出现,意味着有大量的连接正在被创建和断开。此外,TIME_WAIT会短暂地占用端口,严重时会使端口耗竭出现can't assign requested address的错误

值得一提的是,我在 Reddit 的一篇 文章 中,也发现了与此类似的现象。

项目使用的 InfluxDB 客户端(github.com/influxdata/influxdb1-client/v2)已经有些年头了。它的工作原理相当直接:通过创建 Go 标准库中的 HTTP 客户端,对 InfluxDB 的 HTTP API 发起 POST 请求。出现大量的 TIME_WAIT 状态,这通常是由于 HTTP 中的 transport 配置 MaxIdleConnsPerHost/MaxIdleConns 设置不当所导致的。其次go http client还有一个坑,必须将连接中的数据使用io.ReadAll读完才能Close,否则连接也不会复用。

在 HTTP 客户端中,transport 的角色是进行连接管理,它包含了连接池和管理逻辑。具体在这篇文章中可找到更多的信息。

通过阅读 InfluxDB 客户端的代码,发现它直接创建了 transport

代码语言:javascript
复制
tr := &http.Transport{
    TLSClientConfig: &tls.Config{
        InsecureSkipVerify: conf.InsecureSkipVerify,
    },
    Proxy: conf.Proxy,
}
if conf.TLSConfig != nil {
    tr.TLSClientConfig = conf.TLSConfig
}
...
return &client{
    ...
    transport: tr,
    encoding:  conf.WriteEncoding,
}, nil

令人惊讶的是,它没有提供给用户配置 MaxIdleConnsPerHost/MaxIdleConns 的方法。通过fork这份代码完善,在项目中将这两个配置都改成合适的数字,TIME WAIT暴增就解决了。但这是一个值得深入探究的问题。

transport的配置:

代码语言:javascript
复制
    // MaxIdleConnsPerHost, if non-zero, controls the maximum idle
    // (keep-alive) connections to keep per-host. If zero,
    // DefaultMaxIdleConnsPerHost is used.
    MaxIdleConnsPerHost int

    // MaxConnsPerHost optionally limits the total number of
    // connections per host, including connections in the dialing,
    // active, and idle states. On limit violation, dials will block.
    //
    // Zero means no limit.
    MaxConnsPerHost int

默认的 DefaultMaxIdleConnsPerHost 为 2,这是一个相当保守的配置。而 MaxConnsPerHost 则为无穷大。如果服务作为 HTTP 客户端,在短时间内向另一个服务发起数千次请求,会发生以下情况:

  1. 虽然 HTTP 1.1 连接可以keep alive,但不能多路复用,这会创建大量的连接。如果是 HTTP 2,就不会有这个问题,这也是为什么 gRPC 只需要一个连接就能维持很高的吞吐量。
  2. 在请求结束后,由于暂时没有发送/接收数据,transport会认为连接已经空闲。而默认的最大空闲连接数为 2,这导致只会保留 2 个连接,而将其他的全部主动关闭。

在 TCP 中,主动关闭连接的一方最终会进入 TIME_WAIT 状态。虽然 TIME_WAIT 本身并没有什么害处,但大量的连接创建和销毁会增加性能的开销。

因此,MaxIdleConnsPerHost 是一个非常重要的配置,与 HTTP 客户端的性能密切相关。在向同一个服务发起大量请求的客户端上,MaxIdleConnsPerHost 可以理解为客户端维持的最终连接数。在执行 netstat -anp|grep EST|wc -l 时,你会发现 EST 状态的连接数和 MaxIdleConnsPerHost 差不多。

通常,MaxConnsPerHost 的配置应等于或略大于 MaxIdleConnsPerHost。如果这个值配置得过小,当连接达到阈值时,会阻塞连接的创建并进行等待,从而影响网络的吞吐量。如果 MaxConnsPerHost 配置得过大,而 MaxIdleConnsPerHost 配置得过小,则会引发大量的连接创建和销毁。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-01-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档