原文地址:http://joyexpr.com/2013/11/22/c100k-4-kernel-tuning/
早期的系统,系统资源包括CPU、内存等都是非常有限的,系统为了保持公平,默认要限制进程对资源的使用情况。由于Linux的默认内核配置无法满足C100K的要求,因此需要对其进行适当的调优。
我们可以通过 ulimit
查看一下典型的机器默认的限制情况:
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 204800
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 204800
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
比如其中的 open files
,默认一个进程能打开的文件句柄数量为1024,对于一些需要大量文件句柄的程序,如web服务器、数据库程序等,1024往往是不够用的,在句柄使用完毕的时候,系统就会频繁出现emfile错误。
俗话说:一个巴掌拍不响,要完成 C100K
的目标,需要服务器端与客户端的紧密配合,下面将分别对这二者的调优进行介绍。
客户端
1:文件句柄数量受限
在Linux平台上,无论是编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,由于每个TCP连接都要创建一个socket句柄,而每个socket句柄同时也是一个文件句柄,所以其最高并发数量要受到系统对用户单一进程同时可打开文件数量的限制以及整个系统可同时打开的文件数量限制。
1.1:单一进程的文件句柄数量受限
我们可以ulimit命令查看当前用户进程可打开的文件句柄数限制:
[root@localhost ~]# ulimit -n
1024
这表示当前用户的每个进程最多允许同时打开1024个文件,除去每个进程必然打开的标准输入、标准输出、标准错误、服务器监听socket、进程间通讯的unix域socket等文件,剩下的可用于客户端socket连接的文件数就只有大概1024-10=1014个左右。也就是说,在默认情况下,基于Linux的通讯程序最多允许同时1014个TCP并发连接。
对于想支持更高数量的TCP并发连接的通讯处理程序,就必须修改Linux对当前用户的进程可同时打开的文件数量的软限制(soft limit)和硬限制(hardlimit)。其中:
- 软限制是指Linux在当前系统能够承受的范围内进一步限制用户能同时打开的文件数。
- 硬限制是指根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量。
通常软限制小于或等于硬限制,可通过ulimit命令查看软限制和硬限制:
[root@localhost ~]# ulimit -Sn
1024
[root@localhost ~]# ulimit -Hn
4096
修改单一进程能同时打开的文件句柄数有2种方法:
1、直接使用ulimit命令,如:
[root@localhost ~]# ulimit -n 1048576
执行成功之后,ulimit n、Sn、Hn的值均会变为1048576。但该方法设置的值只会在当前终端有效,且设置的值不能高于方法2中设置的值。
2、对 /etc/security/limits.conf
文件,添加或修改:
* soft nofile 1048576
* hard nofile 1048576
其中,
*
代表对所有用户有效,若仅想针对某个用户,可替换星号。- soft即软限制,它只是一个警告值。
- hard代表硬限制,是一个真正意义的阈值,超过就会报错。
- nofile表示打开文件的最大数量。
- 1048576 = 1024 * 1024,为什么要取这个值呢?因为
在linux kernel 2.6.25之前通过ulimit -n(setrlimit(RLIMIT_NOFILE))设置每个进程的最大打开文件句柄数不能超过NR_OPEN(1024*1024),也就是100多w(除非重新编译内核),而在25之后,内核导出了一个sys接口可以修改这个最大值(/proc/sys/fs /nr_open).具体的changelog在https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=9cfe015aa424b3c003baba3841a60dd9b5ad319b
注意文件保存之后,需要注销或重启系统方能生效。
1.2:整个系统的文件句柄数量受限
解决完单一进程的文件句柄数量受限问题后,还要解决整个系统的文件句柄数量受限问题。我们可通过以下命令查看Linux系统级的最大打开文件数限制:
[root@localhost ~]# cat /proc/sys/fs/file-max
98957
file-max表示系统所有进程最多允许同时打开的文件句柄数,是Linux系统级硬限制。通常,这个系统硬限制是Linux系统在启动时根据系统硬件资源状况计算出来的最佳的最大同时打开文件数限制,如果没有特殊需要,不应该修改此限制。
要修改它,需要对 /etc/sysctl.conf
文件,增加一行内容:
fs.file-max = 1048576
保存成功后,需执行下面命令使之生效:
[root@localhost ~]# sysctl -p
2:端口数量受限
解决完文件句柄数量受限的问题后,就要解决IP端口数量受限的问题了。一般来说,对外提供请求的服务端不用考虑端口数量问题,只要监听某一个端口即可。可客户端要模拟大量的用户对服务端发起TCP请求,而每一个请求都需要一个端口,为了使一个客户端尽可能地模拟更多的用户,也就要使客户端拥有更多可使用的端口。
由于端口为16进制,即最大端口数为2的16次方65536(0-65535)。在Linux系统里,1024以下端口只有超级管理员用户(如root)才可以使用,普通用户只能使用大于等于1024的端口值。
我们可以通过以下命令查看系统提供的默认的端口范围:
[root@localhost ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768 61000
即只有61000-32768=28232个端口可以使用,即单个IP对外只能同时发送28232个TCP请求。
修改方法有以下2种:
1、执行以下命令:
echo "1024 65535"> /proc/sys/net/ipv4/ip_local_port_range
该方法立即生效,但重启后会失效。
2、修改 /etc/sysctl.conf
文件,增加一行内容:
net.ipv4.ip_local_port_range = 1024 65535
保存成功后,需执行下面命令使之生效:
[root@localhost ~]# sysctl -p
修改成功后,可用端口即增加到65535-1024=64511个,即单个客户端机器只能同时模拟64511个用户。要想突破这个限制,只能给该客户端增加IP地址,这样即可相应成倍地增加可用IP:PORT数。具体可参考yongboy的这篇文章。
服务端
1:文件描述符数量受限
同客户端的问题1。
2:TCP参数调优
要想提高服务端的性能,以达到我们高并发的目的,需要对系统的TCP参数进行适当的修改优化。
方法同样是修改 /etc/sysctl.conf
文件,增加以下内容:
net.ipv4.tcp_tw_reuse = 1
当服务器需要在大量TCP连接之间切换时,会产生大量处于TIME_WAIT状态的连接。TIME_WAIT意味着连接本身是关闭的,但资源还没有释放。将net_ipv4_tcp_tw_reuse设置为1是让内核在安全时尽量回收连接,这比重新建立新连接要便宜得多。
net.ipv4.tcp_fin_timeout = 15
这是处于TIME_WAIT状态的连接在回收前必须等待的最小时间。改小它可以加快回收。
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
提高TCP的最大缓冲区大小,其中:
net.core.rmem_max
:表示接收套接字缓冲区大小的最大值(以字节为单位)。
net.core.wmem_max
:表示发送套接字缓冲区大小的最大值(以字节为单位)。
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
提高Linux内核自动对socket缓冲区进行优化的能力,其中:
net.ipv4.tcp_rmem
:用来配置读缓冲的大小,第1个值为最小值,第2个值为默认值,第3个值为最大值。
net.ipv4.tcp_wmem
:用来配置写缓冲的大小,第1个值为最小值,第2个值为默认值,第3个值为最大值。
net.core.netdev_max_backlog = 4096
每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。默认为1000。
net.core.somaxconn = 4096
表示socket监听(listen)的backlog上限。什么是backlog呢?backlog就是socket的监听队列,当一个请求(request)尚未被处理或建立时,他会进入backlog。而socket server可以一次性处理backlog中的所有请求,处理后的请求不再位于监听队列中。当server处理请求较慢,以至于监听队列被填满后,新来的请求会被拒绝。默认为128。
net.ipv4.tcp_max_syn_backlog = 20480
表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_syncookies = 1
表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭。
net.ipv4.tcp_max_tw_buckets = 360000
表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000。
net.ipv4.tcp_no_metrics_save = 1
一个tcp连接关闭后,把这个连接曾经有的参数比如慢启动门限snd_sthresh、拥塞窗口snd_cwnd,还有srtt等信息保存到dst_entry中,只要dst_entry没有失效,下次新建立相同连接的时候就可以使用保存的参数来初始化这个连接。
net.ipv4.tcp_syn_retries = 2
表示在内核放弃建立连接之前发送SYN包的数量,默认为4。
net.ipv4.tcp_synack_retries = 2
表示在内核放弃连接之前发送SYN+ACK包的数量,默认为5。
完整的TCP参数调优配置如下所示:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 4096
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 20480
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 360000
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
其它一些参数
vm.min_free_kbytes = 65536
用来确定系统开始回收内存的阀值,控制系统的空闲内存。值越高,内核越早开始回收内存,空闲内存越高。
vm.swappiness = 0
控制内核从物理内存移出进程,移到交换空间。该参数从0到100,当该参数=0,表示只要有可能就尽力避免交换进程移出物理内存;该参数=100,这告诉内核疯狂的将数据移出物理内存移到swap缓存中。