linux服务器实现百万并发

并发量和承载的概念

并发量:一个服务器能同时承载客户端的数量

承载:客户端发送给服务器的请求(http或tcp等)在200ms内可以返回正确的结果

影响并发量的主要因素

  1. 数据库响应速度
  2. 网络带宽响应
  3. 内存操作
  4. 日志性能

服务器能同时建立的连接数量只是并发的基础,服务器处理客户端的请求包括请求、响应、处理和关闭。

网络五元组

  1. 源ip
  2. 目的ip
  3. 源端口号
  4. 目的端口号
  5. 协议

​ 由于只有一台客户端不断建立连接,所以源ip是一样的;目的ip也只有一台服务器;源端口号是不确定的,由操作系统或自己指定,目的端口号则是指定的8080端口;协议使用tcp协议。此时只有源端口号是不唯一的。这五个元素只要有一个是不同的,就对应一个不同的fd。

第一次并发量测试

使用之前的epoll_reactor.c的服务器代码进行测试,使用网络调试助手进行连接测试,成功连接,发生两次数据,然后断开。

wang@ubuntu:~/Desktop/0vicevip/5.1$ gcc epoll_reactor.c -o epoll_reactor
wang@ubuntu:~/Desktop/0vicevip/5.1$ ./epoll_reactor 8080
recv from 192.168.211.1 at port 57216
Recv: http://www.cmsoft.cn, 20 Bytes
Recv: http://www.cmsoft.cn, 20 Bytes
disconnect 5

再将客户端连接代码mulport_client_epoll.c中的最大连接端口宏定义先修改为1

编译之后,先启动服务端再启动客户端

wang@ubuntu:~/Desktop/0vicevip/5.1$ ./mulport 192.168.211.128 8080
connections: 999, sockfd:1002, time_used:1915
socket: Too many open files
error : Too many open files

查看单进程可操作性文件描述符数

根据错误提示可知,是由于打开太多文件导致的,那么我们先查看一下当前系统允许操作的文件描述符数量。

wang@ubuntu:~/Desktop/0vicevip/5.1$ 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) 15391
max locked memory       (kbytes, -l) 65536
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) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 15391
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

open files (-n) 1024可得,最多打开的文件描述符个数为1024

修改单进程可操作性文件描述符数

有两种方法

  1. 使用ulimit -n 数字可以进行临时修改,重启当前终端后,修改效果消失
  2. 修改/etc/security/limits.conf配置文件中的内容

在文件尾部加入中间两行配置

#<domain>      <type>  <item>         <value>

*       hard     nofile         1048576     ===》Hard limit限制,下面的设置不能超过该数
*       soft     nofile         1048576     ===#说明domain:域 记录那个用户组的程序有效 * 所有用户 root:root用户
type:soft(软限制) hard(硬限制) 软:能超过value,但是超过后操作系统回回收,硬不能超过设置的数量
value:对应数量

但是有个bug,切换后没有效果,重启也没有办法,切换到root用户,再切换回来就生效了,太奇怪了。每次这么搞好麻烦,还不如直接方法1修改,或者一直用root。

修改系统能打开的最大文件描述符数

同时还有个地方需要修改,就是在/etc/sysctl.conf中,需要设置系统可以打开的文件描述符最大值。

# 系统最大文件打开数
fs.file-max = 20000000
 
# 单个用户进程最大文件打开数
fs.nr_open = 20000000

指向sysctl -p /etc/sysctl.conf后生效,可通过cat /proc/sys/fs/file-max查看。

第二次并发量测试

再次运行服务端和客户端

root@ubuntu:/home/wang/Desktop/0vicevip/5.1# ./mulport 192.168.211.128 8080
connections: 999, sockfd:1002, time_used:1857
connections: 1999, sockfd:2002, time_used:1898
connections: 2999, sockfd:3002, time_used:1908
connections: 3999, sockfd:4002, time_used:1881
connections: 4999, sockfd:5002, time_used:1886
connections: 5999, sockfd:6002, time_used:1924
connections: 6999, sockfd:7002, time_used:1950
connections: 7999, sockfd:8002, time_used:1940
connections: 8999, sockfd:9002, time_used:1931
connections: 9999, sockfd:10002, time_used:1956
connections: 10999, sockfd:11002, time_used:3026
connections: 11999, sockfd:12002, time_used:1987
connections: 12999, sockfd:13002, time_used:1992
connections: 13999, sockfd:14002, time_used:1940
connections: 14999, sockfd:15002, time_used:3805
connections: 15999, sockfd:16002, time_used:4060
connections: 16999, sockfd:17002, time_used:4240
connections: 17999, sockfd:18002, time_used:4179
connections: 18999, sockfd:19002, time_used:4220
connections: 19999, sockfd:20002, time_used:5522
connections: 20999, sockfd:21002, time_used:4605
connections: 21999, sockfd:22002, time_used:5540
connections: 22999, sockfd:23002, time_used:4621
connections: 23999, sockfd:24002, time_used:4711
connections: 24999, sockfd:25002, time_used:5049
connections: 25999, sockfd:26002, time_used:4931
connections: 26999, sockfd:27002, time_used:5046
connections: 27999, sockfd:28002, time_used:6185
connect: Cannot assign requested address
error : Cannot assign requested address

观察最后三行,并发量到达2.8W,连接出错。客户端提示无法分配地址,这是由于客户端端口限制导致的,系统分配给程序的端口已经用完了,linux系统默认0-1023为常用端口,1024-49151为注册服务的端口,49152-65535为自定义端口。

修改可用端口范围

此端口的范围可以通过range_from来修改。

#方法1 cat /proc/sys/net/ipv4/ip_local_port_range
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# cat /proc/sys/net/ipv4/ip_local_port_range
32768	60999
#方法2 在/etc/sysctl.conf配置文件中直接修改
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# sysctl -p /etc/sysctl.conf
net.ipv4.ip_local_port_range = 32768 65000
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# cat /proc/sys/net/ipv4/ip_local_port_range
32768	65000
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# 

再次测试

connections: 31999, sockfd:32002, time_used:7004
connect: Cannot assign requested address
error : Cannot assign requested address

由于客户端可用端口号增加了4000,所以连接数也增加了4000,但是依然远远不够,我们可以考虑让服务端监听100个端口,我们通过客户端来访问这一百个端口。服务端端的代码同时也做出修改。(实际开发中不会采用这种方式提高并发量,一般使用多进程fork的方式来实现(只能使用进程不能使用线程,因为每个进程都有自己的文件系统空间,但是线程是共享的))

第三次并发量测试

connections: 102999, sockfd:103002, time_used:2009
connections: 103999, sockfd:104002, time_used:2133
 RecvBuffer:Hello Server: client --> 103998
data from 4
data from 5
data from 6
data from 7
data from 8
data from 9
data from 10
。。。
data fro
 Error clientfd:141002, errno:11
connect: Connection refused
error : Connection refused

连接数到达十万,由于网络协议栈中的netfilter组件配置限制的原因。

netfilter

客户端发送的data,由网卡通过sk_buffer组件传输到网络协议栈,到达网络协议栈时,会经过一个netfilter组件(操作系统为防止大量无效的数据攻击,在数据进入网络协议栈之前加一个层过滤器netfilter规则,对网络数据包进行过滤)

在这里插入图片描述

Linux系统中的iptables防火墙就是基于netfilter实现的。iptables一共两部分,一部分是内核实现的netfilter的接口,有一部分是提供出来应用程序可以用的东西,即通过iptables命令操作的一些内容(转发、拒绝某个接口数据等功能)。

/etc/sysctl.conf中添加如下配置,然后sysctl -p /etc/sysctl.conf生效即可。

cat /proc/sys/net/netfilter/nf_conntrack_max查看允许连接数。

//最大跟踪连接数,默认是65536个
net.nf_conntrack_max = 1048576
//稳定的连接状态最多保留多久,单位是秒,默认是432000秒,就是5天
net.netfilter.nf_conntrack_tcp_timeout_established = 1200

性能调优

修改了系统对fd的限制,单个进程对fd的限制,接下来就是内存的限制,tcp相关收发缓冲区,tcp内存设置,以及tcp相关参数设置。

# 全连接队列长度,默认128
net.core.somaxconn = 10240
# 半连接队列长度,当使用sysncookies无效,默认128
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_syncookies = 0
 
# 网卡数据包队列长度  
net.core.netdev_max_backlog = 41960
 
# time-wait 最大队列长度
net.ipv4.tcp_max_tw_buckets = 300000
 
# time-wait 是否重新用于新链接以及快速回收
net.ipv4.tcp_tw_reuse = 1  
net.ipv4.tcp_tw_recycle = 1
 
# tcp报文探测时间间隔, 单位s
net.ipv4.tcp_keepalive_intvl = 30
# tcp连接多少秒后没有数据报文时启动探测报文
net.ipv4.tcp_keepalive_time = 900
# 探测次数
net.ipv4.tcp_keepalive_probes = 3
 
# 保持fin-wait-2 状态多少秒
net.ipv4.tcp_fin_timeout = 15  
 
# 最大孤儿socket数量,一个孤儿socket占用64KB,当socket主动close掉,处于fin-wait1, last-ack
net.ipv4.tcp_max_orphans = 131072  
 
# 每个套接字所允许得最大缓存区大小
net.core.optmem_max = 819200
 
# 默认tcp数据接受窗口大小
net.core.rmem_default = 262144  
net.core.wmem_default = 262144  
net.core.rmem_max = 16777216  
net.core.wmem_max = 16777216
 
# tcp栈内存使用第一个值内存下限, 第二个值缓存区应用压力上限, 第三个值内存上限, 单位为page,通常为4kb
net.ipv4.tcp_mem = 786432 4194304 8388608
# 读, 第一个值为socket缓存区分配最小字节, 第二个,第三个分别被rmem_default, rmem_max覆盖
net.ipv4.tcp_rmem = 4096 4096 4206592
# 写, 第一个值为socket缓存区分配最小字节, 第二个,第三个分别被wmem_default, wmem_max覆盖
net.ipv4.tcp_wmem = 4096 4096 4206592

tcp_mem

  • low:当TCP使用了低于该值的内存页面数时,TCP不会考虑释放内存。
  • pressure:当TCP使用了超过该值的内存页面数量时,TCP试图稳定其内存使用,进入压力模式,当内存消耗低于low值时则退出压力状态。
  • high:允许所有tcp sockets用于排队缓冲数据报的页面量,当内存占用超过此值,系统拒绝分配socket,后台日志输出“TCP: too many orphaned sockets”或“Out of socket memory”

每个tcp连接对应一个读缓冲区recvbuffer和写缓冲区sendbuffer,可以使用修改配置文件,也可以通过在程序中使用setsockopt函数。

tcp_rmem 读缓冲区

  • low:默认是4096,缓冲区的最小大小
  • pressure:默认是87380,接收缓冲区的初始大小
  • high:允许接收缓冲区的最大大小

tcp_wmem 写缓冲区

  • low:默认是4096,缓冲区的最小大小
  • pressure:默认是16384,发送缓冲区的初始大小
  • high:允许发送缓冲区的最大大小
高并发设计原则

自己的服务器程序,调试网络协议栈参数的原则:

  1. 传输文件数据量大时,将buffer调大。
  2. 数据量较小时,可将buff调小,当buffer为512时,ssh无法连接,且日志也无法打印。调小的目的,就是为了增加并发量,可以把fd的数量做到更多,占用的内存更少。

第四次并发量测试

因为只有一台设备,也懒得程序装额外的虚拟机,所以所有的测试都是在同一台上。最终的测试结果达到了70万连接。(当时虚拟机已经卡住了,我一看情况不对,赶紧先把图截下来,哈哈哈)伪c1000k并发完成!

在这里插入图片描述

抖动

每k连接建立,所用的时长差距较大。原因是只有一个线程在处理accept,处理不过来,半连接队列已满,只能等待。

解决方法

  1. accept和recv/send分开。将网络连接丢给一个线程去处理,分配fd后,交给线程池处理。参考redis
  2. 开多个线程accept,参考nginx
c1000k服务器客户端流程

在这里插入图片描述

C10M应该如何实现

C1000k,只需使用一个epoll的reactor模型就可以实现,那么如何实现千万级的并发呢?要解决这个问题,最重要就是跳过内核协议栈的冗长路径,把网络包直接送到要处理的应用程序那里去。有两种常见的机制,DPDK 和 XDP。

(1) DPDK:是用户态网络的标准。它跳过内核协议栈,直接由用户态进程通过轮询的方式,来处理网络接收。每时每刻都有新的网络包需要处理,轮询的优势就很明显。

(2) XDP:是 Linux 内核提供的一种高性能网络数据路径。它允许网络包,在进入内核协议栈之前,就进行处理,也可以带来更高的性能。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值