Golang基准测试REST API时报socket: too many open files错误
socket: too many open files 错误排查与修复 错误现象
对基于Golang实现的REST API做基准测试时,程序抛出文件描述符耗尽错误:
根因分析
该错误本质是单进程可打开的文件描述符(FD)配额被耗尽,每个TCP Socket连接都会占用1个FD,高并发压测场景下FD消耗速度超过回收速度就会触发该错误,结合现有代码具体诱因有3个:
- 客户端实例重复创建:如果
NewHttpCardPaymentService()不是在服务启动时全局单例初始化,而是每次处理请求时都调用,会导致每次请求都新建独立的resty客户端实例,各实例的连接池完全隔离,连接无法复用,还会产生大量孤立连接造成FD泄漏。 - 强制短连接配置加剧FD消耗:代码中显式设置了
Connection: close请求头,强制每次请求完成后关闭TCP连接,关闭的连接会进入TIME_WAIT状态,默认Linux内核会保留该状态60秒才释放对应的FD,压测QPS较高时TIME_WAIT连接会快速堆积耗尽FD。 - 系统默认FD配额不足:Linux系统默认单进程open files配额通常为1024,完全无法支撑高并发压测场景的连接需求。
修复方案
1. 修正HTTP客户端使用逻辑,启用连接复用
- 将
HttpCardPaymentService改为服务启动时初始化的全局单例,禁止每次处理请求时新建实例,保证全局共用同一个resty客户端,让连接池机制正常生效。 - 删除请求配置中的
SetHeader("Connection", "close")代码,移除强制短连接逻辑,允许TCP连接复用。 - 显式给resty客户端配置合理的连接池参数,参考实现:
func NewHttpCardPaymentService() *HttpCardPaymentService { client := resty.New(). SetTimeout(5 * time.Second). SetTransport(&http.Transport{ MaxIdleConns: 100, // 全局最大空闲连接数 MaxIdleConnsPerHost: 100, // 单目标主机最大空闲连接数 IdleConnTimeout: 90 * time.Second, // 空闲连接超时回收时间 }) return &HttpCardPaymentService{ Client: client, } }
2. 调大系统文件描述符上限
压测环境需要将单进程FD上限调大到足够支撑并发量:
- 临时生效(当前shell会话有效,重启后失效):执行命令
ulimit -n 65535,再在该会话中启动服务与压测程序。 - 永久生效:编辑
/etc/security/limits.conf文件,添加以下配置:
* soft nofile 65535 * hard nofile 65535
保存后重新登录服务器即可生效。
3. 压测环境可选TCP参数优化
如果存在大量TIME_WAIT连接堆积,可调整内核参数加快连接回收:
编辑/etc/sysctl.conf添加以下配置:
net.ipv4.tcp_tw_reuse = 1 // 允许将TIME_WAIT状态的连接重新用于新的TCP连接 net.ipv4.tcp_fin_timeout = 30 // 缩短FIN-WAIT-2状态连接的回收时间
执行sysctl -p命令让配置立即生效。
注意:Linux 4.12及以上版本内核已移除
tcp_tw_recycle参数,请勿配置该参数;上述TCP参数调整仅建议在压测环境使用,生产环境需根据实际业务场景评估后配置。
内容的提问来源于stack exchange,提问作者Vinicius Barbosa de Medeiros
相关产品推荐
相关产品推荐

