再谈 TCP 的 CLOSE_WAIT

再谈 TCP 的 CLOSE_WAIT

背景

某日集群告警,hbase regionserver 因 fd 不足导致进程主动退出,简单排查后发现regionserver 到 datanode 的TCP 连接存在大量 CLOSE_WAIT,单机总数有10万之多,众所周知,CLOSE_WAIT 产生的原因在于收到 FIN 请求后没有调用 close()函数导致,回顾一下 TCP 的正常关闭过程:

无论出于任何原因程序没有执行 close,我们可以借助操作系统的keepalive机制将这些不活跃的连接关闭掉。在一个空闲的 TCP 连接上,不执行任何收发数据的操作,当另一端掉电的时候,TCP 无法发现连接出现问题,keepalive机制会在多久没有收到数据之后发送探测包来进行检测,从而避免类似问题。

keepalive

要启用 TCP 的 keepalive 机制,必须在 socket 中设置了以下选项的时候才会生效:

发送方或接受方进行设置都可以生效,最终由设置了该选项了一段发送keepalive探测包(实际上是一个 flag 为 ACK 的空包),如果在 server 端设置,可以设置在监听的套接字上,accept 的新连接会继承这个该属性。

在 linux 下,keepalive 有如下三个参数进行调整(以及他们的默认值):

这三个参数的含义,误的建议参考《UNIX网络编程》,按照默认值,上述三个参数的正确理解为:在一个TCP 连接上,keepalive_time时间内没有任何数据包传输,则开启keepalive的一段发送keepalive包,如果经过tcp_keepalive_time的时间后没有收到应答,则间隔tcp_keepalive_intvl的时间再次发送。经过tcp_keepalive_probes的次数后仍然没有收到应答,则发送 RST包关闭该连接。

如果对端正常回复了keepalive消息,则下一次keepalive包在等待keepalive_time时间后发送。

你可以通过 sysctl -w 来修改内核参数,或者修改/etc/sysctl.conf 后 sysctl -p 让参数生效,两种方式均无需重启系统或应用程序,且对当前已建立的 socket 动态生效。

虽然keepalive可以只在一端启用,但是仍然建议在 socket 两端同时进行设置。因为当一端调用 close 后,这段就不会再发送keepalive包。例如只有服务端开启,而客户端没有开启,当服务端调用了 close,服务端就不会再发送keepalive包,而如果客户端收到 FIN 后没有执行关闭,连接仍然会长期处于 CLOSE_WAIT状态。

keepalive && CLOSE_WAIT

搞清楚了 tcp 的keepalive,我们再看看keepalive与CLOSE_WAIT 关系。当CLOSE_WAIT最初产生时,主动关闭的一端处于FIN_WAIT_1或 FIN_WAIT_2状态,一般FIN_WAIT_1很快会结束,因为对端返回 ack 一般很快(长时间的FIN_WAIT_1与tcp_orphan_retries参数有关)。在主动关闭一端处于FIN_WAIT_2状态时,可以正常返回对端的keepalive消息,当FIN_WAIT_2超时,主动关闭这段会彻底释放这个连接,这时,对端的keepalive消息再发生过来之后,由于本端已关闭,会给对方回复一个 RST,对方收到这个 RST 后,协议栈就会清理这个 CLOSE_WAIT 状态的连接。

那么 FIN_WAIT_2超时时间是多久?在 linux 下,由 net.ipv4.tcp_fin_timeout参数决定,默认为60秒。

上图的例子中,服务端监听7788端口,收到对方数据后就调用 close,然后持续 sleep,客户端开启 keepalive,keepalive_time设置为5,可以看到最后一个数据包是服务端对 keepalive 消息返回了 RST

如何解决问题?

回归本文主题,通过设置 socket 选项开启 keepalive机制,同时调整相关内核参数可以让系统清理掉 CLOSE_WAIT 状态的连接,但是具体到 hbase 与 hdfs 的问题上,即使 CLOSE_WAIT 被清理掉了,regionserver 与 hdfs 仍然会存在大量连接。regionserver open region 的时候,要读取所有的 hfile 文件,这些 fd(socket)没有被关闭掉,留着后续 scan 的时候使用,其余正常的读写使用 pread,单独开一条 socket。因此这些 CLOSE_WAIT 的连接本来是 EST 的,即使客户端正确处理了异常,或者通过 keepalive 清理了连接,本质上 regionserver 与 hdfs 之间存在了过多的 TCP 连接,因此最终两种处理方式:

  • 进行 compation,降低 hfile 数量
  • open region 之后关闭 fd,scan 的时候重新打开(或者 scan 走 mr)。

重点回顾

  1. CLOSE_WAIT的连接可以通过系统底层的keepalive机制来关闭
  2. 开启keepalive必须在socket上单独setsockopt,否则内核参数怎么调都没用
  3. 调整内核参数后对当前已建立的连接立即生效。

参考

https://huoding.com/2014/11/06/383
https://blog.csdn.net/ctthuangcheng/article/details/8596818
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt

(转载请注明作者和出处 easyice.cn ,请勿用于任何商业用途)

1 Star2 Stars3 Stars4 Stars5 Stars (欢迎评分)
Loading...

发表评论

邮箱地址不会被公开。 必填项已用*标注