服务端百万并发测试
服务端并发的概念
并发量:同时承载的客户端的数量,实质上就是同时能够维护的 socket 的数量
比如要实现10W客户端同时在线,200ms内正常返回结果,承载量的影响因素:
- 数据库
- 网络带宽
- 内存操作
- 日志
测试服务器十万、百万并发能力的意义更多地在于测试服务器是否具备这种能力,它并不是与业务直接相关的。
常见单机服务模型
- 单线程同步:NTP
- 多线程同步:Natty
- 纯异步:Redis、HAProxy
- 半同步半异步:Natty
- 多进程同步:fastcgi
- 多线程异步:memcached
- 多进程异步:Nginx
- 一个请求一个进程/线程:Apache/CGI
- 微进程框架:erlang/go/lua
- 协程框架:libco/ntyco
并发测试方法
实际上,我们之前基于epoll实现的Reactor模型的简单echo服务器就应经可以实现几十万量级的并发了,我们可以测试一下。测试之前需要先修改一下描述符数量的限制,否则可用的描述符资源会耗尽。服务端测试代码为之前这篇博文中的Reactor模型的代码。
socket数量的限制(描述符数量的限制)
两种修改方法:
-
ulimit -n <value>
这种方法不能持久,重启后就得重新设置 -
vim /etc/security/limits.conf
limits.conf文件的格式如下:
- domain就是指用户;
- 限制策略的type分soft软和hard硬,所谓hard就是硬性限制,数量达到value这个上限就不分配了;soft相对宽松,达到上限后开始回收;
- item:对于文件描述符的数量限制,其对应的item就是
nofile - max number of open files
增加两行:
* soft nofile 1048576
* hard nofile 1048576
客户端测试代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define MAX_CONNECTION 340000 // 单个客户端建立34W个连接,3个客户端就有100W
#define MAX_BUFSIZE 128
#define MAX_EPOLLSIZE (384*1024)
#define MAX_PORT 100 // 最大端口数量
#define TIME_MS_USED(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000) // 用于计算耗时
/* 设置fd为非阻塞 */
static int fdSetNonBlock(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if(flags < 0) return flags;
flags |= O_NONBLOCK;
if(fcntl(fd, F_SETFL, flags) < 0) return -1;
return 0;
}
/* 设置socket SO_REUSEADDR*/
static int sockSetReuseAddr(int sockfd)
{
int reuse = 1;
return setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
}
struct epoll_event events[MAX_EPOLLSIZE];
int main(int argc, char *argv[])
{
if(argc < 3)
{
printf("Usage: %s ip port", argv[0]);
return 0;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int connections = 0; // 建立连接的计数,用于统计
char buffer[MAX_BUFSIZE] = {
0};
int i;
int clientFinishTest = 0;
int portOffset = 0;
int epfd = epoll_create(1);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
struct timeval loopBegin, loopEnd;
gettimeofday(&loopBegin, NULL);
while(1)
{
struct epoll_event ev;
int sockfd = 0;
if(connections < MAX_CONNECTION