1. 原因分析
一台 Nginx server,到晚上高峰 messages 出现大量的如下信息:
Apr 23 22:43:21 rs1 kernel: [...] Out of socket memory
两种情况会出发 "Out of socket memory" 的信息:
1.有很多的孤儿套接字(orphan sockets)
2.tcp socket 用尽了给他分配的内存
首先看看情况 2。对于 TCP socket 来说,使用 pages 来计数的,而非 bytes,一般情况下 1 page = 4096 bytes。page 大小可以通过下面命令获得:$ getconf PAGESIZE
4096
查看内核分配了多少的内存给 TCP:$ cat /proc/sys/net/ipv4/tcp_mem
69618 92825 139236
第一个数字表示,当 tcp 使用的 page 少于 69618 时,kernel 不对其进行任何的干预
第二个数字表示,当 tcp 使用了超过 92825 的 pages 时,kernel 会进入 “memory pressure”
第三个数字表示,当 tcp 使用的 pages 超过 139236 时,我们就会看到题目中显示的信息
查看 tcp 实际用的内存:$ cat /proc/net/sockstat
sockets: used 116
TCP: inuse 3 orphan 0 tw 4 alloc 4 mem 110
UDP: inuse 1 mem 1
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
可以看到,实际使用的 mem(110) 远远小于 69618,所以,“Out of socket memory”的错误是由于第一种情况引起的。
关于 orphan socket 的解释,请看这里。orphan socket 对于应用程序来说,意义不大,这也是内核要限制被 orphan socket 消耗内存的原因。而对于 web server 来说,有大量的 orphan socket 也属正常,那么多的连接放在那儿了。
查看 orphan socket 限制:$ cat /proc/sys/net/ipv4/tcp_max_orphans
对比当前系统中的:$ cat /proc/net/sockstat
sockets: used 14565
TCP: inuse 35938 orphan 21564 tw 70529 alloc 35942 mem 1894
由于内核代码中有个位运算,所以实际的跟最大的是 2x 或者是 4x 的关系。现在根据实际情况,将 tcp_max_orphans 调到一个合理的值就可以了。原则上该值建议只增大,另外,每个 orphan 会消耗大概 64KB 的内存。还有个叫 tcp_orphan_retries 参数,对于 web server,可以减小。
2. tcp_mem参数调整
在服务端,连接达到一定数量,诸如50W时,有些隐藏很深的问题,就不断的抛出来。 通过查看dmesg命令查看,发现大量TCP: too many of orphaned sockets错误,也很正常,下面到了需要调整tcp socket参数的时候了。
第一个需要调整的是tcp_rmem,即TCP读取缓冲区,单位为字节,查看默认值
cat /proc/sys/net/ipv4/tcp_rmem 4096 87380 4161536
默认值为87380 byte ≈ 86K,最小为4096 byte=4K,最大值为4064K。
第二个需要调整的是tcp_wmem,发送缓冲区,单位是字节,默认值
cat /proc/sys/net/ipv4/tcp_wmem 4096 16384 4161536
解释同上
第三个需要调整的tcp_mem,调整TCP的内存大小,其单位是页,1页等于4096字节。系统默认值:
cat /proc/sys/net/ipv4/tcp_mem 932448 1243264 1864896
tcp_mem(3个INTEGER变量):low, pressure, high
low:当TCP使用了低于该值的内存页面数时,TCP不会考虑释放内存。
pressure:当TCP使用了超过该值的内存页面数量时,TCP试图稳定其内存使用,进入pressure模式,当内存消耗低于low值时则退出pressure状态。
high:允许所有tcp sockets用于排队缓冲数据报的页面量,当内存占用超过此值,系统拒绝分配socket,后台日志输出“TCP: too many of orphaned sockets”。
一般情况下这些值是在系统启动时根据系统内存数量计算得到的。 根据当前tcp_mem最大内存页面数是1864896,当内存为(1864896*4)/1024K=7284.75M时,系统将无法为新的socket连接分配内存,即TCP连接将被拒绝。
实际测试环境中,据观察大概在99万个连接左右的时候(零头不算),进程被杀死,触发out of socket memory错误(dmesg命令查看获得)。每一个连接大致占用7.5K内存(下面给出计算方式),大致可算的此时内存占用情况(990000 * 7.5 / 1024K = 7251M)。
这样和tcp_mem最大页面值数量比较吻合,因此此值也需要修改。
三个TCP调整语句为:
echo "net.ipv4.tcp_mem = 786432 2097152 3145728">> /etc/sysctl.conf echo "net.ipv4.tcp_rmem = 4096 4096 16777216">> /etc/sysctl.conf echo "net.ipv4.tcp_wmem = 4096 4096 16777216">> /etc/sysctl.conf
备注: 为了节省内存,设置tcp读、写缓冲区都为4K大小,tcp_mem
三个值分别为3G 8G 16G,tcp_rmem
和tcp_wmem
最大值也是16G。
经过若干次的尝试,最终达到目标,1024000个持久连接。1024000数字是怎么得来的呢,两台物理机器各自发出64000个请求,两个配置为6G左右的centos测试端机器(绑定7个桥接或NAT连接)各自发出640007 = 448000。也就是 1024000 = (64000) + (64000) + (640007) + (64000*7), 共使用了16个网卡(物理网卡+虚拟网卡)。
终端输出
...... online user 1023990 online user 1023991 online user 1023992 online user 1023993 online user 1023994 online user 1023995 online user 1023996 online user 1023997 online user 1023998 online user 1023999 online user 1024000
在线用户目标达到1024000个!
服务启动时内存占用:
total used free shared buffers cached
Mem: 10442 271 10171 0 22 78
-/+ buffers/cache: 171 10271
Swap: 8127 0 8127
系统达到1024000个连接后的内存情况(执行三次 free -m 命令,获取三次结果):
total used free shared buffers cached
Mem: 10442 7781 2661 0 22 78
-/+ buffers/cache: 7680 2762
Swap: 8127 0 8127
total used free shared buffers cached
Mem: 10442 7793 2649 0 22 78
-/+ buffers/cache: 7692 2750
Swap: 8127 0 8127
total used free shared buffers cached
Mem: 10442 7804 2638 0 22 79
-/+ buffers/cache: 7702 2740
Swap: 8127 0 8127
这三次内存使用分别是7680,7692,7702,这次不取平均值,取一个中等偏上的值,定为7701M。那么程序接收1024000个连接,共消耗了 7701M-171M = 7530M内存, 7530M*1024K / 1024000 = 7.53K, 每一个连接消耗内存在为7.5K左右,这和在连接达到512000时所计算较为吻合。
虚拟机运行Centos内存占用,不太稳定,但一般相差不大,以上数值,仅供参考。
执行top -p 某刻输出信息:
top - 17:23:17 up 18 min, 4 users, load average: 0.33, 0.12, 0.11
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.2%us, 6.3%sy, 0.0%ni, 80.2%id, 0.0%wa, 4.5%hi, 8.8%si, 0.0%st
Mem: 10693580k total, 6479980k used, 4213600k free, 22916k buffers
Swap: 8323056k total, 0k used, 8323056k free, 80360k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2924 yongboy 20 0 82776 74m 508 R 51.3 0.7 3:53.95 server
执行vmstate:
vmstat procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 2725572 23008 80360 0 0 21 2 1012 894 0 9 89 2 0
获取当前socket连接状态统计信息:
cat /proc/net/sockstat sockets: used 1024380 TCP: inuse 1024009 orphan 0 tw 0 alloc 1024014 mem 2 UDP: inuse 11 mem 1 UDPLITE: inuse 0 RAW: inuse 0 FRAG: inuse 0 memory 0
获取当前系统打开的文件句柄:
sysctl -a | grep file fs.file-nr = 1025216 0 1048576 fs.file-max = 1048576
此时任何类似于下面查询操作都是一个慢,等待若干时间还不见得执行完毕。
netstat -nat|grep -i "8000"|grep ESTABLISHED|wc -l
netstat -n | grep -i "8000" | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
以上两个命令在二三十分钟过去了,还未执行完毕,只好停止。
本次从头到尾的测试,所需要有的linux系统需要调整的参数也就是那么几个,汇总一下:
echo "* - nofile 1048576" >> /etc/security/limits.conf
echo "fs.file-max = 1048576" >> /etc/sysctl.conf
echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.conf
echo "net.ipv4.tcp_mem = 786432 2097152 3145728" >> /etc/sysctl.conf
echo "net.ipv4.tcp_rmem = 4096 4096 16777216" >> /etc/sysctl.conf
echo "net.ipv4.tcp_wmem = 4096 4096 16777216" >> /etc/sysctl.conf
其它没有调整的参数,仅仅因为它们暂时对本次测试没有带来什么影响,实际环境中需要结合需要调整类似于SO_KEEPALIVE、tcpmax_orphans等大量参数。
sysctl -p /etc/sysctl.conf 加载修改后的tcp相关参数
附目前服务器的参数设置
echo "net.ipv4.tcp_mem = 786432 1048576 1572864" >> /etc/sysctl.conf
echo "net.ipv4.tcp_rmem = 4096 87380 8388608" >> /etc/sysctl.conf
echo "net.ipv4.tcp_wmem = 4096 87380 8388608" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_tw_buckets = 500000" >> /etc/sysctl.conf
echo "net.ipv4.tcp_tw_recycle = 1" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_orphans = 262144" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog = 20480" >> /etc/sysctl.conf
echo "net.ipv4.tcp_synack_retries = 1" >> /etc/sysctl.conf
echo "net.ipv4.tcp_syn_retries = 1" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog = 20480" >> /etc/sysctl.conf
echo "net.ipv4.tcp_synack_retries = 1" >> /etc/sysctl.conf
echo "net.ipv4.tcp_syn_retries = 1" >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
内核相关参数说明如下:
# TCP and memory optimization
# increase TCP max buffer size setable using setsockopt()
#it's already auto-tuned very well by Linux based on the amount of RAM.
#net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_rmem = 4096 87380 8388608
net.ipv4.tcp_wmem = 4096 87380 8388608
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_syn_backlog = 65536
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_syncookies = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1
#修改系統默认的 TIMEOUT 时间。
net.ipv4.tcp_fin_timeout = 30
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.tcp_keepalive_time = 1200
#表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为10000到65000。(注意:这里不要将最低值设的太低,否则可能会占用掉正常的端口!)
net.ipv4.ip_local_port_range = 10000 65000
#表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,TIME_WAIT将立刻被清除并打印警告信息。默 认为180000,改为6000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于 Squid,效果却不大。此项参数可以控制TIME_WAIT的最大数量,避免Squid服务器被大量的TIME_WAIT拖死。
net.ipv4.tcp_max_tw_buckets = 6000
所有的TCP/IP参数都位于/proc/sys/net目录下(请注意,对/proc/sys/net目录下内容的修改都是临时的,任何修改在系统重启后都会丢失)
/etc/sysctl.conf是一个允许你改变正在运行中的Linux系统的接口。它包含一些TCP/IP堆栈和虚拟内存系统的高级选项,可用来控制Linux网络配置,由于/proc/sys/net目录内容的临时性,建议把TCPIP参数的修改添加到/etc/sysctl.conf文件, 然后保存文件,使用命令“/sbin/sysctl –p”使之立即生效