大家好,又见面了,我是你们的朋友全栈君。
随着访问量的增大,系统默认的承受能力达到上限,这个时候就会报一些异常。比如/var/log/messages中常见的“kernel: TCP: time wait bucket table overflow”这个信息,会发现每隔5s就会报出几行。此时查看连接状态如下:
[root@ZhOu ~]# netstat -an | awk '{print $6}' | sort | uniq -c | sort -rn
16539 TIME_WAIT
3159 ESTABLISHED
23 LISTEN
20 CONNECTED
5 STREAM
4
1 I-Node
1 Foreign
1 established)
1 and
1 9793
1 938869322
1 8751
1 7183
1 7182
1 7168
1 10007
可以看到TIME_WAIT的量还是很高的。下面先看一下TCP连接的过程
通过此图先说明几个概念:
TIME_WAIT的产生条件:主动关闭方在发送四次挥手的最后一个ACK会变为TIME_WAIT状态,保留次状态的时间为两个MSL(linux里一个MSL为30s,是不可配置的)
TIME_WAIT两个MSL的作用:可靠安全的关闭TCP连接。比如网络拥塞,主动方最后一个ACK被动方没收到,这时被动方会对FIN开启TCP重传,发送多个FIN包,在这时尚未关闭的TIME_WAIT就会把这些尾巴问题处理掉,不至于对新连接及其它服务产生影响。
TIME_WAIT占用的资源:少量内存(查资料大概4K)和一个fd。
TIME_WAIT关闭的危害:1、 网络情况不好时,如果主动方无TIME_WAIT等待,关闭前个连接后,主动方与被动方又建立起新的TCP连接,这时被动方重传或延时过来的FIN包过来后会直接影响新的TCP连接;
2、 同样网络情况不好并且无TIME_WAIT等待,关闭连接后无新连接,当接收到被动方重传或延迟的FIN包后,会给被动方回一个RST包,可能会影响被动方其它的服务连接。
打印“time wait bucket table overflow”信息的代码:
void tcp_time_wait(struct sock *sk, int state, int timeo)
{
struct inet_timewait_sock *tw = NULL;
const struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcp_sock *tp = tcp_sk(sk);
int recycle_ok = 0;
if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);
if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
tw = inet_twsk_alloc(sk, state);
if (tw != NULL) {
struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
....
} 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套接字关闭流程,此时本机主动关闭tcp套接字,套接字状态=变为time wait,等待对端进行关闭套接字。
从代码可以看出,有两种情况可能导致这样的打印:
1、当tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets时,即当当前处于time wait状态的socket数量超过sysctl_max_tw_buckets(即net.ipv4.tcp_max_tw_buckets)时
2、当inet_twsk_alloc(sk, state)返回为NULL时,出现这样的打印,再看看inet_twsk_alloc(sk, state)的代码:
struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk, const int state)
{
struct inet_timewait_sock *tw =
kmem_cache_alloc(sk->sk_prot_creator->twsk_prot->twsk_slab,
GFP_ATOMIC);
...
return tw;
}
由此可以看出,当kmem_cache_alloc(从slab中分配内存)失败时,会返回NULL,最终导致这样的打印,这种情况可能的原因为内存不足。
综上,打印出现有两种可能原因:
1) 处于time wait状态的tcp套接字,超过net.ipv4.tcp_max_tw_buckets限制
2) 申请内存失败,可能原因为内存不足
该打印不影响系统的正常功能,当出现这种情况时,出现异常的套接字打印完成后会立即释放,不再等对端确认关闭。但有隐患,如果表示处于time wait的socket太多,则可能导致无法创建新的连接。这种可能是业务代码在socket关闭的处理上有些问题,可能存在大量半关闭的socket。另外,也可能表示内存不足,需要关注。
解决方法
既然知道了TIME_WAIT的用意了,尽量按照TCP的协议规定来调整,对于tw的reuse、recycle其实是违反TCP协议规定的,服务器资源允许、负载不大的条件下,尽量不要打开,当出现TCP: time wait bucket table overflow,需要调整/etc/sysctl.conf中下面参数:
net.ipv4.tcp_max_tw_buckets = 50000
调大timewait 的数量
net.ipv4.tcp_fin_timeout = 10
如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2 状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180 秒,3你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB 服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2 的危险性比FIN-WAIT-1 要小,因为它最多只能吃掉1.5K 内存,但是它们的生存期长些。
net.ipv4.tcp_tw_recycle= 1
启用timewait 快速回收
net.ipv4.tcp_tw_reuse = 1
开启重用,允许将TIME-WAIT sockets 重新用于新的TCP 连接
net.ipv4.tcp_keepalive_time = 15
当keepalive 启用的时候,TCP 发送keepalive 消息的频度,缺省是2 小时
设置完毕,查看messages中的信息和连接状态,一切正常了。
参考文章
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/132818.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...