Linux下Http高并发参数优化之TCP参数

2019-08-20 08:56:42

在上一篇Linux参数优化之TCP/IP基础中,提到了Unix/Linux 基本哲学之一就是 "一切皆文件"。要提高 TCP 并发量,需要调整文件句柄。

文件句柄


因为 Linux 系统为每个 TCP 建立连接时,都要创建一个 socket 句柄,每个 socket 句柄同时也是一个文件句柄。而系统对用户打开的文件句柄是有限制的,看到这里,也就理解了为什么在高并发时会出现 "too many open files"。

# 查看当前用户允许TCP打开的文件句柄最大数ulimit -n# 修改文件句柄vim /etc/security/limits.conf

* soft nofile 655350
* hard nofile 655350

修改后,退出shell终端窗口,重新登录(不是重启服务器),即可查看到最新的结果。

注意:
soft nofile (软限制)是指Linux在当前系统能够承受的范围内进一步限制用户同时打开的文件数
hard nofile (硬限制)是根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量
通常软限制小于或等于硬限制。
* 表示所有用户,也可以指定具体的用户名
所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max
单个进程打开的文件描述符数不能超过user limit中nofile的soft limit
nofile的hard limit不能超过/proc/sys/fs/nr_open

上面的修改只是对用户的单一进程允许打开的最大文件数进行的设置。但是Linux系统对所有用户打开文件数也有限制(硬限制),一般情况下,不用修改此值。

# 查看所有用户打开文件数的最大限制(此值与硬件配置有关)cat /proc/sys/fs/file-max

但是,文件数再大,随着并发量的上升,也总有用完的时候。这时候再看 “文件都可以用「打开(open) –> 读写(write/read) –> 关闭(close)」模式来进行操作”这句话,就会想到,可以把已经用过的文件句柄释放掉,来减少资源的占用,这就又涉及到了资源重新利用和回收的问题。有时候查看端口有特别多的 TIME_WAIT 时,就是因为连接本身是关闭的,但资源还没有释放。

TCP参数调优


网上有很多关于 tcp_tw_reuse 和 tcp_tw_recycle 的设置,但是很多没有说明场景,有时修改并不一定有好处。详细说明请看 不要在linux上启用net.ipv4.tcp_tw_recycle参数 和 tcp_tw_reuse、tcp_tw_recycle 使用场景及注意事项 。

# 是否允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭,这个可以修改为开启cat /proc/sys/net/ipv4/tcp_tw_reuse 

# 是否开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭cat /proc/sys/net/ipv4/tcp_tw_recycle

所以,以上两个参数,在不是十分确定的场景情况下,不建议修改开启。

可以通过以下几个参数进行优化(个别参数值需要根据服务器配置调整,我的测试服务器配置为8核16G)。

参数默认配置调整配置说明
fs.file-max10485769999999所有进程打开的文件描述符数
fs.nr_open16355901635590单个进程可分配的最大文件数
net.core.rmem_default124928262144默认的TCP读取缓冲区
net.core.wmem_default124928262144默认的TCP发送缓冲区
net.core.rmem_max1249288388608默认的TCP最大读取缓冲区
net.core.wmem_max1249288388608默认的TCP最大发送缓冲区
net.ipv4.tcp_wmem4096 16384 41943044096 16384 8388608TCP发送缓冲区
net.ipv4.tcp_rmem4096 87380 41943044096 87380 8388608TCP读取缓冲区
net.ipv4.tcp_mem384657 512877 769314384657 512877 3057792TCP内存大小
net.core.netdev_max_backlog10005000在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
net.core.optmem_max2048081920每个套接字所允许的最大缓冲区的大小
net.core.somaxconn1282048每一个端口最大的监听队列的长度,这是个全局的参数
net.ipv4.tcp_fin_timeout6030对于本端断开的socket连接,TCP保持在FIN-WAIT-2状态的时间(秒)。对方可能会断开连接或一直不结束连接或不可预料的进程死亡
net.core.netdev_max_backlog100010000在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
net.ipv4.tcp_max_syn_backlog10242048对于还未获得对方确认的连接请求,可保存在队列中的最大数目。如果服务器经常出现过载,可以尝试增加这个数字
net.ipv4.tcp_max_tw_buckets50005000系统在同时所处理的最大timewait sockets数目
net.ipv4.tcp_tw_reuse01是否允许将TIME-WAIT sockets重新用于新的TCP连接
net.ipv4.tcp_keepalive_time7200900表示TCP链接在多少秒之后没有数据报文传输时启动探测报文(发送空的报文)
net.ipv4.tcp_keepalive_intvl7530表示前一个探测报文和后一个探测报文之间的时间间隔
net.ipv4.tcp_keepalive_probes93表示探测的次数

从以上参数可以看出,Linux 内核为 TCP 发送和接收做了缓冲队列,以提高其吞吐量。

以上这些参数都是在 /etc/sysctl.conf 文件中定义的,有的参数在文件中可能没有定义,系统给定了默认值,需要修改的话,直接在文件中添加或修改,然后执行sysctl -p命令让其生效。

注意:
1、参数值并不是设置的越大越好,有的需要考虑服务器的硬件配置,参数对服务器上其它服务的影响等。

本地端口号

有时候我们修改了文件句柄限制数后,错误日志又会提示 "Can’t assignrequested address"。这是因为TCP 建立连接,在创建 Socket 句柄时,需要占用一个本地端口号(与 TCP 协议端口号不一样),相当于一个进程,便于与其它进程进行交互。而Linux内核的TCP/IP 协议实现模块对本地端口号的范围进行了限制。当端口号用尽,就会出现这种错误了。

我们可以修改本地端口号的范围。

# 查看IP协议本地端口号限制cat /proc/sys/net/ipv4/ip_local_port_range#一般系统默认为以下值32768    61000#修改本地端口号vim /etc/sysctl.conf#修改参数net.ipv4.ip_local_port_range = 1024 65000#保存修改后,需要执行sysctl命令让修改生效sysctl -p

注意:
1、net.ipv4.ip_local_port_range的最小值为1024,1024以下的端口已经规划为TCP协议占用,如果想将 TCP 协议端口设置为8080等大端口号,可以将这里的最小值调大。

2、如果存在应用服务端口号大于1024的,应该将 net.ipv4.ip_local_port_range 的起始值修改为大于应用服务端口号,否则服务会报错。

是不是设置了这些参数之后,并发性能就显著提升呢?答案是不一定。还需要看服务是否使用了长连接。


  • 2019-12-29 15:05:57

    php 数组分页 array_slice()函数用法

    今天用到一个函数,非常好用,分享给大家 array_slice() -从数组中取出一段 也就是说用这个函数可以和sql语句一样实现分页,原理是将查询出的数组,取出从指定下标开始到指定长度的数组

  • 2019-12-30 10:17:21

    router-link传递参数,query

    在vue-router中,有两大对象被挂载到了实例this; $route(只读、具备信息的对象); $router(具备功能的函数) 查询字符串: 去哪里 ? <router-link :to="{name:'detail',query:{id:1}}"> xxx </router-link>

  • 2019-12-30 16:48:41

    vue provide/inject详解和用法

    父子组件交互方式多种,props、vuex、 、 emit、localStorage还有就是这个provide/inject了。它适合层级比较深的组件,比如子,子孙,子孙后代的组件有好几个用到父组件的某个属性,就可以用到这个provide/inject,它可以避免写大量繁琐的传值代码 我这里为什么要使用它? 我一个知识库详情父组件中包含了大量的子组件,每个子组件都需要父组件的知识库ID,这时候我不想写大量props,就用到provide/inject进行传值了