测试的时候看到系统日志中不断地出现“TCP: time wait bucket table overflow”的信息。在代码中搜索了一下,看到这条日志是在tcp_time_wait()函数中输出的,输出这条日志是在局部变量tw为NULL的情况下。局部变量tw的默认值为NULL, 在下面的代码中初始化:
if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
tw = inet_twsk_alloc(sk, state);
其中tcp_death_row.tw_count是当前TIME_WAIT状态套接字的数量,tcp_death_row.sysctl_max_tw_buckets的值是系统参数tcp_max_tw_buckets的值,如果前者小于后者,则tw的值为NULL。假设分配内存失败,tw的值也为NULL。所以在TIME_WAIT套接字数量超过系统限制或者内存不足时,就会输出“TCP: time wait bucket table overflow”的日志信息,如下所示:
if (tw != NULL) {
......
} else {
/* Sorry, if we're out of memory, just CLOSE this
* socket up. We've got bigger problems than
* non-graceful socket closings.
*/
LIMIT_NETDEBUG(KERN_INFO "TCP: time wait bucket table overflow\n");
}
我这里的问题是因为tcp_max_tw_buckets的值设置的太小了,调整大了之后就OK了。如果系统当前TIME_WAIT状态的套接字数量小于系统限制,出现这样的问题就是严重的内存泄露了。后来google了一下看怎么减小TIME_WAIT套接字的数量,设置的选项大致就是摘要中提到的那几个选项,不过大多数人都是几乎全都给设置,也根本没有考虑是否有用或者是否跟自己的业务相符合,看了内核代码之后发现这些选项并不是什么情况下都能减小time-wait套接字的数量,有些甚至会适得其反,下面来看内核是如何处理的。
一、tcp_max_tw_buckets参数
tcp_max_tw_buckets参数是系统中TIME_WAIT套接字的最大数量。假如tcp_max_tw_buckets的值设的太小,否则会导致部分连接没法进入TIME_WAIT状态,TCP连接可能会不正常关闭,数据包会重传。假如tcp_max_tw_buckets的值设置的太大,TIME_WAIT状态套接字占用的内容可能很大。这个值通常设置的大一些比较好,利用空间来给内核足够的时间来清理之前的TIME_WAIT状态的套接字,然后再结合其他参数来减小TIME_WAIT套接字的影响。这个参数对服务器端和客户端都适用。
二、tcp_timestamps参数和tcp_tw_recycle参数
tcp_timestamps参数用来设置是否启用时间戳选项,tcp_tw_recycle参数用来启用快速回收TIME_WAIT套接字。tcp_timestamps参数会影响到tcp_tw_recycle参数的效果。如果没有时间戳选项的话,tcp_tw_recycle参数无效,代码如下:
if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);
如果没有时间戳选项,tp->rx_opt.ts_recent_stamp的值为0,这样局部变量recycle_ok的值为0,在后面就会使用默认的时间TCP_TIMEWAIT_LEN(60s)作为TIME_WAIT状态的时间长度,如下所示:
if (recycle_ok) {
tw->tw_timeout = rto;
} else {
tw->tw_timeout = TCP_TIMEWAIT_LEN;
if (state == TCP_TIME_WAIT)
timeo = TCP_TIMEWAIT_LEN;
}
所以在设置tcp_tw_recycle参数后要检查一下tcp_timestamps参数是否设置。
const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
其中icsk->icsk_rto的值是超时重传的时间,这个值是根据网络情况动态计算的。rto的值为icsk->icsk_rto的3.5倍。在网络比较好的情况下,rto的值会小于TCP_TIMEWAIT_LEN,从而达到加速的目的;但是如果在网络情况比较差,也就是说客户端和服务器端往返的时间比较长的情况下,rto的值有可能会大于TCP_TIMEWAIT_LEN,这种情况下反而适得其反,这种情况通常是由客户端引起的。所以在设置tcp_tw_recycle的时候要考虑到客户端的情况。
tcp_fin_timeout - INTEGER Time to hold socket in state FIN-WAIT-2, if it was closed
by our side. Peer can be broken and never close its side,
or even died unexpectedly. Default value is 60sec.
Usual value used in 2.2 was 180 seconds, you may restore
it, but remember that if your machine is even underloaded WEB server, you risk to overflow memory with kilotons of dead sockets,
FIN-WAIT-2 sockets are less dangerous than FIN-WAIT-1,
because they eat maximum 1.5K of memory, but they tend
to live longer. Cf. tcp_max_orphans.
如果是正常的处理流程就是在FIN_WAIT_2情况下接收到FIN进入到TIME_WAIT的情况,tcp_fin_timeout参数对处于TIME_WAIT状态的时间没有任何影响,但是如果这个参数设的比较小,会缩短从FIN_WAIT_1到TIME_WAIT的时间,从而使连接更早地进入TIME_WAIT状态。状态开始的早,等待相同的时间,结束的也早,客观上也加速了TIME_WAIT状态套接字的清理速度。