c1000k,单机百万并发测试
使用epoll实现的reactor反应堆服务端,开启一个进程多个端口,然后使用多个客户端 ip 多端口来连接
1、在16g内存的笔记本上
2、用四台虚拟机(一台服务器 三台客户端)
3、使用epoll写的reactor反应堆服务端,源码在最下面
4、多线程的客户端,源码在最下面
并发:一个服务器能够同时承载客户端的数量
服务器能同时建立连接的数量 是并发的基础
承载 100w 在200ms内返回
这里回声服务器 没有下列多余影响
1、数据库 2、网络带宽 3、内存操作 4、日志
一、一个连接的定义
1.1 服务器理论最大并发数
一个连接包含五元组,源ip(sip)、目的ip(dip)源端口(sport)、目的端、(dport) 协议
2 的 32 次方(源ip数)× 2的 16 次方(源port数)× 2 的 32 次方(目的ip数)× 2的 16 次方(目的port数)大约等于四百多亿亿亿
(不过每条连接都会消耗服内存,实践中绝不可能达到这个理论数字。)
1.2 测试实验配置
这里使用一个服务端ip * 100服务端端口 * 60000客户端端口 * 3 个客户端 理论上可以达到200w连接 奈何笔记本内存不够
sip 多个客户端 这里采用三个虚拟机(三个ip)
dip 服务器IP只有一个 使用一个虚拟机(一个ip)
sport 客户端数量 使用60000个端口
dport 服务的端口 使用100个端口
proto tcp 使用tcp连接
1.3 linux系统的连接相关默认配置
看看ubuntu20.04的默认配置
root@luo:~# ulimit -n
1024
root@luo:~# sysctl -a |grep mem
...
net.ipv4.tcp_mem = 92880 123843 185760 # 92880* 4k 300m 500m 700m 当协议栈占用空间500m时优化 大于700m时不再分配
net.ipv4.tcp_rmem = 4096 131072 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
...
net.ipv4.ip_local_port_range = 32768 60999
fs.file-max = 9223372036854775807
net.nf_conntrack_max = 65536
1.4下面解释配置的作用
1、net.ipv4.tcp_mem TCP使用了内存页面数
net.ipv4.tcp_rmem 为TCP socket预留用于接收缓冲的内存
net.ipv4.tcp_wmem 为TCP socket预留用于发送缓冲的内存
tcp_wmem tcp_rmem服务器传输大文件就调大 传输字符 调小(但可能ssh 都会很慢) 一个fd就是tcp_wmem tcp_rmem所占用空间 使用更少的内存
2、ulimit -n 和 net.nf_conntrack_max fd 限制 ulimit 限制fd 数量
默认单个进程打开的fd为1000多个 可以修改ulimit -n 1048576(临时修改) 或 /etc/security/limits.conf(永久修改)
accept: Too many open files
3、net.ipv4.ip_local_port_range 默认用户只能用30000以上的端口建立连接 要自己改
建立连接时 服务端端口从三万多开始挨个遍历 找到一个没有被占用的
connections: 27999, sockfd:28002, time_used:3193
connect: Cannot assign requested address
error : Cannot assign requested address
4、net.nf_conntrack_max 服务端建立连接的syn连接数
iptables基于netfilter的应用程序 iptables会调用netfilrer的接口
报文sk_buff从网卡上到达协议栈时会经过netfilter
conntion timeout
二、实验开始
2.1 使用sysctl临时修改配置(重启后失效)
#send buffer 服务器传输大文件就调大 传输字符 调小(但可能ssh 都会很慢) 一个fd就是tcp_wmem tcp_rmem所占用空间 使用更少的内存
sysctl -w net.ipv4.tcp_wmem="2048 2048 4096"
sysctl -w net.ipv4.tcp_rmem="2048 2048 4096"
sysctl -w net.ipv4.tcp_mem="262144 524288 786432" # 262144*4k 1g 2g 3g 当协议栈占用空间2g时优化 大于3g时不再分配
sysctl -w net.ipv4.ip_local_port_range="1025 64000"
ulimit -n 1048576
modprobe ip_conntrack
sysctl -w net.nf_conntrack_max=1048576
2.2 编译最下方源码 开启c/s回声服务器看效果
send[fd=980328], [32]Hello Server: client --> 329478
1、服务端启动后看一下内存
root@luo:~# free
total used free shared buff/cache available
Mem: 8117280 2376860 5343540 1620 396880 5485128
Swap: 4001788 0 4001788
2、服务端维持980000连接时的内存
root@luo:~# free
total used free shared buff/cache available
Mem: 8117280 6973192 103992 0 1040096 913852
Swap: 4001788 1090996 2910792
3、计算单条连接消耗内存
6973192 - 2376860 = 4596332 ÷ 800000= 4.69
由于笔记本开了4台虚拟机一起跑 连接总是到90多万的时候虚拟机崩溃 最多980000
平均计算一个连接4k左右
三、测试中的一些问题以及优化方向
文件描述符fd不够
端口数量不够
accept 全连接队列满了
客户端超时 服务端不会ack 连接队列满了?
半连接全连接 连接队列的问题 满了后需要排队
会导致网络抖动 accept线程池竞争 多线程accept
多线程的网络模型 redis memcached nginx
1、accept与recv/send的fd分开
2、多线程accept
nginx memcached多进程更符合业务处理 固定多线程接入 多线程业务处理
源码
reactor.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define BUFFER_LENGTH 1024
#define MAX_EPOLL_EVENTS 1024*1024 //connection
#define MAX_EPOLL_ITEM 102400 //con
#define SERVER_PORT 8888
#define LISTEN_PORT_COUNT 100
typedef int NCALLBACK(int ,int, void*);
struct ntyevent {
int fd;
int events;
void *arg;
int (*callback)(int fd, int events, void *arg);
int status;
char buffer[BUFFER_LENGTH];
int length;
long last_active;
};
struct ntyreactor {
int epfd;
struct ntyevent *events; // 1024 * 1024
};
int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);
void nty_event_set(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg) {
ev->fd = fd;
ev->callback = callback;
ev->events = 0;
ev->arg = arg;
ev->last_active = time(NULL);
return ;
}
int nty_event_add(int epfd, int events, struct ntyevent *ev) {
struct epoll_event ep_ev = {
0, {
0}};
ep_ev.data.ptr = ev;
ep_ev.events = ev->events = events;
int op;
if (ev->status == 1) {
op = EPOLL_CTL_MOD;
} else {
op = EPOLL_CTL_ADD;
ev->status = 1;
}
if (epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0) {
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
return -1;
}
return 0;
}
int nty_event_del(int epfd, struct ntyevent *ev) {
struct epoll_event ep_ev = {
0, {
0}};
if (ev->status != 1) {
return -1;
}
ep_ev.data.ptr = ev;
ev->status = 0;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev);
return 0;
}
int recv_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor =