内核参数调优

/proc/sys/net/ipv4/tcp_mem
确定TCP栈应该如何反映内存使用,每个值的单位都是内存页(通常是4KB)。第一个值是内存使用的下限;第二个值是内存压力模式开始对缓冲区使用应用压力的上限;第三个值是内存使用的上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的BDP可以增大这些值(注意,其单位是内存页而不是字节)。

tcp_mem(3个INTEGER变量):low, pressure, high

  • low:当TCP使用了低于该值的内存页面数时,TCP不会考虑释放内存。理想情况下,这个值应与指定给tcp_wmem的第2个值相匹配,这第2个值表明,最大页面大小乘以最大并发请求数除以页大小,如131072*300/4096。
  • pressure:当TCP使用了超过该值的内存页面数量时,TCP试图稳定其内存使用,进入pressure模式,当内存消耗低于low值时则退出pressure状态。理想情况下这个值应该是TCP可以使用的总缓冲区大小的最大值,如204800*300/4096。
  • high:允许所有tcp sockets用于排队缓冲数据报的页面量。如果超过这个值,TCP 连接将被拒绝,这就是为什么不要令其过于保守(512000*300/4096)的原因了。 在这种情况下,提供的价值很大,它能处理很多连接,是所预期的2.5倍;或者使现有连接能够传输2.5倍的数据。 我的网络里为192000 300000 732000
1
2
3
4
5
6
7
8
9
10
11
12
static void tcp_init_mem(void)
{
/* nr_free_buffer_pages()计算ZONE_DMA和ZONE_NORMAL的页数,
* 对于64位系统来说,其实就是所有内存了。
*/
unsigned long limit = nr_free_buffer_pages() / 8;
limit = max(limit, 128UL); /* 不能低于128页 */

sysctl_tcp_mem[0] = limit / 4 * 3; /* 最小值设为3/32的系统内存 */
sysctl_tcp_mem[1] = limit; /* 压力值设为1/8的系统内存 */
sysctl_tcp_mem[2] = sysctl_tcp_mem[0] * 2; /* 最大值设为3/16的系统内存 */
}

sndbuf是根据什么定的?业务
在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send()、recv()不断的循环收发。

为了达到最大网络吞吐,socket send buffer size(SO_SNDBUF)不应该小于带宽和延迟的乘积。之前我遇到2个性能问题,都和SO_SNDBUF设置得太小有关。但是,写程序的时候可能并不知道把SO_SNDBUF设多大合适,而且SO_SNDBUF也不宜设得太大,浪费内存。于是,有OS提供了动态调整缓冲大小的功能,这样应用程序就不用再对SO_SNDBUF调优了。(接受缓冲SO_RCVBUF也是类似的问题,不应该小于带宽和延迟的乘积)。

(1)当设置的值val > 最大值sysctl_wmem_max,则设置为最大值的2倍:2sysctl_wmem_max;
(2)当设置的值的两倍val
2 < 最小值,则设置成最小值:SOCK_MIN_SNDBUF;
(3)当设置的值val < 最大值sysctl_wmem_max,且 val2> SOCK_MIN_SNDBUF,则设置成2val。

存放接收缓冲区最大值的位置:/proc/sys/net/core/rmem_max
存放发送缓冲区最大值的位置:/proc/sys/net/core/wmem_max

通过setsockopt设置SO_SNDBUF、SO_RCVBUF这连个默认缓冲区的值,再用getsockopt获取设置的值,发现返回值是设置值的两倍。

Linux从2.4开始支持接收缓冲和发送缓冲的动态调整。

如果指定了tcp_wmem,则net.core.wmem_default被tcp_wmem的覆盖。send Buffer在tcp_wmem的最小值和最大值之间自动调整。如果调用setsockopt()设置了socket选项SO_SNDBUF,将关闭发送端缓冲的自动调节机制,tcp_wmem将被忽略,SO_SNDBUF的最大值由net.core.wmem_max限制。

初步结论:
内核发送缓存区是存在上限的,如果并发连接太多,而且socket的SO_SNDBUF比较大,会导致前面的连接就把内核发送缓存区用完了,导致后面的连接一直发不出数据,此时调小socket的SO_SNDBUF,就可以解决该问题。比如内核发送缓存区总大小为60M,设置socket的SO_SNDBUF为1M,则只能让60M/1M=60个连接同时使用,如果将socket的SO_SNDBUF改为200K,则可以60M/200K=307个连接并发使用。

可修改方案:根据并发连接数动态调整SO_SNDBUF(并发大的时候调小),或者不设置SO_SNDBUF,或者调大/proc/sys/net/ipv4/tcp_mem参数

  1. 采用sar -n DEV 1能更好的观察收发的速度,sar -n SOCK 1可以查看当前使用的socket数。
  2. 除了使用netstat查看tcp连接的收发缓冲区大小,还可以使用ss命令,另外/proc/net/tcp可以看到更详细的tcp连接信息,比如拥塞窗口,重传次数等。
  3. 可以在代码里使用ioctl(fd, SIOCOUTQ, &pending);查看socket发送缓冲区的大小,ioctl(fd, SIOCINQ, &pending);为接收缓冲区,头文件sys/ioctl.h、linux/sockios.h。

限制了接收新 TCP 连接侦听队列的大小。对于一个经常处理新连接的高负载 web服务环境来说,默认的 128 太小了。大多数环境这个值建议增加到 1024 或者更多。 服务进程会自己限制侦听队列的大小(例如 sendmail(8) 或者 Apache),常常在它们的配置文件中有设置队列大小的选项。大的侦听队列对防止拒绝服务 DoS 攻击也会有所帮助。我们可以通过,echo 1000 >/proc/sys/net/core/somaxconn来修改这个参数。

cat /proc/net/sockstat
sockets: used 294
TCP: inuse 38 orphan 0 tw 0 alloc 48 mem 4
UDP: inuse 4 mem 0
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
sockets: used:已使用的所有协议套接字总量
TCP: inuse:正在使用(正在侦听)的TCP套接字数量,其值不大于netstat –lnt | grep ^tcp | wc –l
TCP: orphan:不属于任何进程孤儿TCP连接数(无用、待销毁的TCP socket数)
TCP: tw:等待关闭的TCP连接数,其值等于TIME_WAIT状态链路数
TCP:alloc(allocated):已分配(已建立、已申请到sk_buff)的TCP套接字数量。其值等于netstat –ant | grep ^tcp | wc –l
TCP: mem:套接字缓冲区使用量(单位页大小),对应系统所有tcp链路的内存开销。
UDP: inuse:正在使用的UDP套接字数量
RAW:单纯IP层socket
FRAG:使用的IP段数量

Linux打开文件数量限制:
系统级别:
所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max(只用于查看,不能设置修改),检查系统实际使用了多少文件描述符:cat /proc/sys/fs/file-nr

1. sysctl -w fs.file-max=567962, 运行时临时修改(root用户)
2. vi /etc/sysctl.conf, 永久修改,fs.file-max=567962

用户级别:
单个进程打开的文件描述符数不能超过user limit中nofile的soft limit
nofile的soft limit不能超过其hard limit
nofile的hard limit不能超过/proc/sys/fs/nr_open(1048576)

1. 临时设置,ulimit -Sn 160000,ulimit -Hn 160000,ulimit -n 180000(同时设置soft limit和hard limit)
2. vi /etc/security/limits.conf, 永久修改,* soft nofile 102400,* hard nofile 104800

客户端与服务端每建立一个连接,客户端一侧都会占用一个本地端口(假设没有启用SO_REUSEADDR选项),本地端口数量是有限制的(默认是net.ipv4.ip_local_port_range=32768 61000),也就是说在没设置socket的SO_REUSEADDR选项时,一台Linux服务器作为客户端(注意是作为客户端)默认只能建立大概3万个TCP连接(服务端没有这个限制),可以更改net.ipv4.ip_local_port_range增大作为客户端可发起的并发连接数,但最多不会超过65535个(服务端没有这个限制)。

当Linux服务器作为客户端频繁建立TCP短连接时,本地会可能会产生很多TIME_WAIT状态的连接,客户端侧的TIME_WAIT状态的连接会占用一个本地端口直到达到2MSL(最长分解生命期)的时间,这样会导致本地端口被暂时占用,当短连接建立速度过快时(例如做压测时),会导致Cannot assign requested address错误,解决办法有几种,比如像下面这样设置端口复用(复用TIME_WAIT状态的连接)。
打开文件 /etc/sysctl.conf,增加以下设置:

1
2
3
net.ipv4.tcp_syncookies = 1 #表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 #表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

Linux 更多性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃,不应该大于255,默认值是5,对应于180秒左右时间   
net.ipv4.tcp_syn_retries=2
#TCP三次握手过程中,内核在放弃连接之前向客户端发送SYN+ACK包的数量
net.ipv4.tcp_synack_retries=2
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时
net.ipv4.tcp_keepalive_time=1200
#TCP连接被丢弃之前,内核进行重试的次数(负载很大的web服务器,那么也许需要降低该值,这类sockets可能会耗费大量的资源)
net.ipv4.tcp_orphan_retries=3
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
net.ipv4.tcp_fin_timeout=30
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_syn_backlog = 4096
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
#减少超时前的探测次数
net.ipv4.tcp_keepalive_probes=5
#优化网络设备接收队列
net.core.netdev_max_backlog=3000

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!