[原创]结合案例深入解析orphan socket产生与消亡(一)

原创文章,转载请注明:来自云栖社区

本文看点:结合服务器运行案例和TCP代码分析orphan socket产生与消亡以及对系统的影响。精彩的部分在(二)细节分析章节。

问题背景

tengine服务器发生过多次orphan socket数量很多的情况,例如有一次使用ss -s命令查看:

$ss -s
Total: 36569 (kernel 36823)
TCP:   142787 (estab 28586, closed 4842, orphaned 92575, synrecv 9198, timewait 4684/5073), ports 9868

总共142787条TCP连接,处于orphaned状态的连接达到92575。某天orphan socket数量监控占总量百分比曲线图如下:

[原创]结合案例深入解析orphan socket产生与消亡(一)

10:30-10:49 约持续19分钟较高,然后恢复。

dmesg命令可以查看到有大量的提示:
[791.931967] Out of socket memory

orphan socket 做关键词google一把有很多,但没有查找到能详细说明来龙去脉的,

[原创]结合案例深入解析orphan socket产生与消亡(一)

先来看一下在网上关于orphan sockets一段较好的介绍。

Do you have "too many" orphan sockets?
First of all: what's an orphan socket? It's simply a socket that isn't associated to a file descriptor. For instance, after you close() a socket, you no longer hold a file descriptor to reference it, but it still exists because the kernel has to keep it around for a bit more until TCP is done with it. Because orphan sockets aren't very useful to applications (since applications can't interact with them), the kernel is trying to limit the amount of memory consumed by orphans, and it does so by limiting the number of orphans that stick around. If you're running a frontend web server (or an HTTP load balancer), then you'll most likely have a sizeable number of orphans, and that's perfectly normal.

简要理解即orphan sockets是没有与任何文件描述符关联的socket,应用程序已经不能与此socket进行交互了,但是由于内核中TCP未完成,仍然占用TCP的内存暂时不能释放。读完这段还是丈二和尚摸不着头脑――摸不清底细。

对于维护线上稳定性需要搞清楚如下问题:

 什么原因或条件下会导致出现这么多orphan socket ?

 orphan socket的连接处于TCP状态的那一个阶段?

 orphan socket过多会给线上带来什么风险?

原因定位

以某台机器为例看一下TCP各状态的连接数量。

$ss -s
Total: 37964 (kernel 38570)
TCP:   146848 (estab 29556, closed 5114, orphaned 94904, synrecv 9402, timewait 4823/5226), ports 10946

orphaned数量达到 94904;

查看close调用后TCP连接各状态socket数量:(为什么直接查close调用后的状态请参细节分析部分,另外因以下命令执行时间上的偏差可能会对不上ss -s的数值)

$ss -nat | grep LAST-ACK | wc -l
67128

$ss -nat | grep FIN-WAIT-2 | wc -l
11712

$ss -nat | grep FIN-WAIT-1 | wc -l
12317

$ss -nat | grep CLOSE-WAIT | wc -l
25

$ss -nat | grep TIME-WAIT | wc -l
1410

top10输出分析ip:port的连接占用: 以下命令输出中第一列代表IP:PORT ,第二列代表TCP连接数量。

LAST_ACK状态的top10的IP与PORT输出

$ss -ant | grep LAST-ACK | awk "{++S[\$4]} END {for(a in S) print a, S[a]}" |sort -t " " -k 2 -n -r | head -n 10
10x.xxx.xxx.x38:51892 62190
10x.xxx.xxx.x38:51814 381
10x.xxx.xxx.x38:59206 97
10x.xxx.xxx.x38:56107 94
10x.xxx.xxx.x38:58271 92
10x.xxx.xxx.x38:59800 85
10x.xxx.xxx.x38:50851 55
10x.xxx.xxx.x38:52226 45
10x.xxx.xxx.x38:52199 27
10x.xxx.xxx.x38:57017 25

FIN-WAIT-1状态的top10的IP与PORT输出

$ss -ant | grep FIN-WAIT-1 | awk "{++S[\$4]} END {for(a in S) print a, S[a]}" |sort -t " " -k 2 -n -r | head -n 10
10x.xxx.xxx.x38:51892 5581
10x.xxx.xxx.x38:51814 1208
10x.xxx.xxx.x38:59206 782
10x.xxx.xxx.x38:58271 749
10x.xxx.xxx.x38:56107 724
10x.xxx.xxx.x38:59800 611
10x.xxx.xxx.x38:50851 285
10x.xxx.xxx.x38:52199 142
10x.xxx.xxx.x38:50127 97
10x.xxx.xxx.x38:52435 65

FIN-WAIT-2状态的top10的IP与PORT输出

$ss -ant | grep FIN-WAIT-2 | awk "{++S[\$4]} END {for(a in S) print a, S[a]}" |sort -t " " -k 2 -n -r | head -n 10
10.xxx.xxx.38:51814 1825
10.xxx.xxx.38:59800 1684
10.xxx.xxx.38:58271 1661
10.xxx.xxx.38:59206 1563
10.xxx.xxx.38:56107 1558
10.xxx.xxx.38:50851 329
10.xxx.xxx.38:52199 261
10.xxx.xxx.38:50127 203
10.xxx.xxx.38:52631 144
10.xxx.xxx.38:55488 114

可以明确看出这台主机上10x.xxx.xxx.x38:51892 占用了 62190 条socket处于LAST_ACK状态。
为什么这么多LAST_ACK状态的连接?看一下TCP状态迁移图,可知LAST_ACK状态代表被动关闭的一端在发送FIN报文后,最后等待另一端的ACK报文。

[原创]结合案例深入解析orphan socket产生与消亡(一)

正常流程客户端TCP状态迁移:

CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
正常流程服务器TCP状态迁移:

CLOSED->LISTEN->SYN_RCVD->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED

此机器是做为反向代理服务器符合上述正常流程服务器TCP状态迁移,第一个现象:通过监控查看10x.xxx.xxx.x38:51892对应的http流量,发现此用户每秒的新建TCP连接数量是1.5K~2K,且设置的流量带宽限制在20Mbps,并在报文出口方向出现大量的丢包;第二个现象:http访问日志中记录大量的http 499状态码的访问连接。

根据以上两个现象推断:10x.xxx.xxx.x38:51892收到client端的关闭FIN后会发送ACK,进入到CLOSE_WAIT 状态,应用程序中如果没有数据待发送会调用close()使TCP连接发送FIN,进入LAST_ACK状态,因为出口流量设置的限制大量的ACK和FIN报文被丢弃,导致出现上述62190 条socket处于LAST_ACK状态。

后来与用户沟通得知client端是移动端APP程序,其TCP连接读写超过(限流量没有收到数据等)会主动关闭连接,可以佐证日志中大量出现的499状态码。

到此原因分析完成,可以使用解决方案中调整服务器的内核参数解决,如果需要更详细了解orphaned socket可以详细阅读下一篇的细节分析。

解决方案

 如上细节分析中某些场景下会对当前sock做惩罚,将当前orphan 的数量 2x 甚至 4x与系统限制值做比较。这样内核日志会打印出Out of socket memory,但实际的数量并没有超过系统的限制值,属于误报的一种情形,可以根据具体的情况,将 tcp_max_orphans 内核参数值适当调大。

 系统中net.ipv4.tcp_orphan_retries 的默认值是 0,内核重置为8次, 需要将tcp_orphan_retries改为较小的数值,可大大降低orphans的数量,降低tcp内存的占用量。注意:设置较小的数值,可以有效降低orphans的数量(net.ipv4.tcp_orphan_retries = 0并不是想像中的不重试)

案例小结

内核日志dmesg命令看到 Out of socket memory,出现内存不足可能会有两种情况:

 有太多的 orphan sockets,通常对于一些负载较重的服务器经常会出现这种情况。

 分配给 TCP 的内存确实较少,从而导致内存不足。

第一种情况,可以用ss -s命令或cat /proc/net/sockstat查看是否孤儿套接字过多。

第二种情况,可以调整TCP的内存,因网上有大量的此类分析解决,这里不再详述。

最后来回答一下对于线上稳定性来说需要搞清楚的问题:

  1. orphan socket的连接处于TCP状态的那一个阶段?

TCP_FIN_WAIT1、TCP_LAST_ACK、TCP_CLOSING状态都可归类计数于orphan socket,但当通过TCP_LINGER2或sysctl_tcp_fin_timeout设置的超时时间大于60秒时的TCP_FIN_WAIT2的连接也归类计数于orphan socket;小于60秒的TCP_FIN_WAIT2状态的连接则归类计数于TIME_WAIT,从代码可以看出TCP_TIME_WAIT状态是不计入orphan socket;TCP_CLOSE_WAIT 状态的连接既不计入orphan socket 也不计入TIME_WAIT。

  1. 什么原因或条件下会导致出现这么多orphan socket ?
  2. orphan socket过多会给线上带来什么风险?

TCP_FIN_WAIT1 和 TCP_LAST_ACK 状态的连接都在等待对方回复ACK,例如client端可以对产生的连接故意发送FIN半关闭,而不回复最后的ACK使服务器端产生大量LAST_ACK,消耗TCP资源,这种情况下调整tcp_max_orphans参数和tcp_orphan_retries可以限制简单的DDOS攻击。

上一篇:结合案例深入解析orphan socket产生与消亡(二)


下一篇:[原创]有关tengine几个超时时间说明