有一台 etcd,突然出现了响应缓慢的情况。排查中发现,系统 CPU,带宽,IO,内存,rlimit 都还有很大余量。但是 ss 发现,端口的backlog 堆积了大量连接。
$ ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port ... LISTEN 745 65535 10.20.30.40:2379 *:* ...
但是看并没有 SYN_RECV 状态的连接存在。所以不像由于并发连接太多造成的来不及 accept,而是 etcd 本身处理 accept
的过程慢。统计 ESTABLISHED 连接数,发现数量总是大致在 10000 个左右。这个连接数看起来很整,怀疑是不是有什么特殊的限制。
于是去看 etcd 的代码,发现 etcdmain/etcd.go
中,启动 etcd 的代码里有
l = transport.LimitListener(l, int(fdLimit-reservedInternalFDNum))
这个 LimitListener 限制了最多同时服务的客户端连接数,超过时就会等待已有连接退出。传入的 fdLimit 是获取的 open files rlimit 限制。reservedInternalFDNum 是保留的 fd 数,是个等于 150 的常量。看起来意图是在 fd 不够用的时候,保证自身的 IO 和集群通信不受影响。
后来了解到,ulimit 原本是 10240,后来调整到了 102400,当时为了不重启进程,动态修改了运行中的 etcd 的 rlimit。没想到 etcd 在启动的时候还使用这个值做了自己的限制。虽然 rlimit 改了,但是由于进程没重启,etcd 还是使用的原来的值做的限制。最后重启 etcd 就恢复了正常。