问题分析:引入新elastic api导致的TIME_WAIT堆积
- 2019 年 11 月 2 日
- 筆記
之前使用github.com/olivere/elastic库遇到了一个TIME_WAIT堆积的问题,因为问题比较共性(引入新库、性能测试、TIME_WAIT原理),所以简单记录下,新同学可以关注下
目录
- 发生背景:业务引入新elastic api
- 问题原因:http短连导致TIME_WAIT堆积
- 解决方法:合理设置/net/http连接池大小
- 思考反思:引入新库需谨慎,必须提前做功能和压力测试
- 相关扩展:TIME_WAIT状态有必要存在吗?
- 相关扩展:导致大量TIME_WAIT的常见原因和解决方案
发生背景:业务引入新elastic api
之前业务调用ES是走原生RESTful,用golang的net/http直接写客户端。由于这种方式要自己拼表达式,所以有同学就引入了github.com/olivere/elastic,对表达式封装了一层,让代码更加简单高效,示例:
但是模块发布后,陆续发现服务请求ES无响应,导致服务不可用,立即回滚先恢复。
问题原因:http短连导致TIME_WAIT堆积
明确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链接复用,并发数大的话很容易就超了,连接池拿不到链接的话就默认新创建短连了
解决方法:合理设置/net/http连接池大小
解决大量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
连接池大小要设置多少算够?这里一个比较简单的实践:
- 单个连接QPS = 1s/平均延迟
- 最大连接数 = 业务QPS/单个连接QPS + 一点富余量
可以参考:https://partners-intl.aliyun.com/help/doc-detail/98726.htm
思考反思:引入新库需谨慎,必须提前做功能和压力测试
开发过程很多同学会引入各种各样的第3方库,帮忙团队提高研发效率,但引入前必须提前做好:
- 团队评估:关注使用普及度、业内反馈等
- 功能测试:关注边缘变量、异常处理等
- 压力测试:关注性能消耗,资源占用等
相关扩展:TIME_WAIT状态有必要存在吗?
根据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这个端口起了新连接,新连接收到上个连接的旧分片可能会导致异常
相关扩展:导致大量TIME_WAIT的常见原因和解决方案
由此可见,如果SVR短期内有大量RPC短链请求,或者访问量大的WebSvr(主动断开链接)都容易导致大量TIME_WAIT产生
常见的解决方案:
- 开启socket重用
- 开启快速回收
- 改短连接为长连接
- 调整TIME_WAIT相关参数
几个相关参数:
- net.ipv4.tcp_tw_reuse:socket重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
- net.ipv4.tcp_tw_recycle:快速回收。允许处于TIME-WAIT状态的socket快速回收到100左右,默认为0,表示关闭
- net.ipv4.tcp_timestamps:记录单调递增时间戳,默认为1,表示开启
- net.ipv4.tcp_max_tw_buckets:允许最大TIME_WAIT连接数,在TIME_WAIT数量超过后,不会有新的TIME_WAIT产生(设置< 65535做降级保护)
- net.ipv4.tcp_fin_timeout:MSL时间,修改系默认的TIMEOUT 时间
ps:reuse和recycle必须在timestamps开启后才有用。但是不建议在NAT环境中启用,它会引起相关问题,可参考:
https://blog.51cto.com/hld1992/2285410
几个相关命令:
- 查看配置: /sbin/sysctl -a
- 修改配置: vi /etc/sysctl.conf
- 重新加载sysctl配置: /sbin/sysctl -p