之前使用github.com/olivere/elastic库遇到了一个TIME_WAIT堆积的问题,因为问题比较共性(引入新库、性能测试、TIME_WAIT原理),所以简单记录下,新同学可以关注下
之前业务调用ES是走原生RESTful,用golang的net/http直接写客户端。由于这种方式要自己拼表达式,所以有同学就引入了github.com/olivere/elastic,对表达式封装了一层,让代码更加简单高效,示例:
但是模块发布后,陆续发现服务请求ES无响应,导致服务不可用,立即回滚先恢复。
明确ES本身没问题后,查看服务机器发现非常多调用ES的链接处在TIME_WAIT状态,命令实例:
[root@TENCENT64 ~]# netstat -n | grep "111.111.111.111:9200" | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 2
TIME_WAIT 10503
太多的TIME_WAIT链接占满了端口65535的限制,导致新链接无法发起。
这里开始抠github.com/olivere/elastic 的源码,为啥已用了全局client还会导致大量TIME_WAIT:
可以看到,/olivere/elastic的ESClient最后也是调用了/net/http库,那核心就是看下他怎么管理http的client了:
可以得出初步结论:/olivere/elastic的ESClient使用了/net/http的默认全局http.DefaultClient,http.DefaultClient底层通讯Transport默认使用了DefaultTransport,而DefaultTransport初始化没设置 MaxIdleConnsPerHost,于是采了默认的DefaultMaxIdleConnsPerHost=2。即只支持2个tcp链接复用,并发数大的话很容易就超了,连接池拿不到链接的话就默认新创建短连了
解决大量TIME_WAIT的方法有很多,针对这个case,这边需在初始化/olivere/elastic/client的时候,设置底层/net/http合理的连接池数量,如:
再压测观察链接数就恢复正常了:
[root@TENCENT64 ~]# netstat -n | grep "111.111.111.111:9200" | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 100
连接池大小要设置多少算够?这里一个比较简单的实践:
可以参考:https://partners-intl.aliyun.com/help/doc-detail/98726.htm
开发过程很多同学会引入各种各样的第3方库,帮忙团队提高研发效率,但引入前必须提前做好:
根据tcp的4次挥手状态转化图,可知主动关闭连接的一方会进入TIME_WAIT,停留2个MSL时间后关闭:
关闭就关闭了,TIME_WAIT状态还要存在的原因:
1、保证完整全双工关闭链接
BadCase:A最后发的ACK丢了,B会重发FIN,如果A没有TIME_WAIT则系统会直接回RST,导致B直接抛异常
RST包:表示复位,直接关闭异常连接,比如服务器没监听的端口收到数据包
2、防止有未接收完的数据包
BadCase:B发完FIN后,之前B的旧数据分片到达(网络波动等影响),这时A这个端口起了新连接,新连接收到上个连接的旧分片可能会导致异常
由此可见,如果SVR短期内有大量RPC短链请求,或者访问量大的WebSvr(主动断开链接)都容易导致大量TIME_WAIT产生
常见的解决方案:
几个相关参数:
ps:reuse和recycle必须在timestamps开启后才有用。但是不建议在NAT环境中启用,它会引起相关问题,可参考:
https://blog.51cto.com/hld1992/2285410
几个相关命令:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。