目錄
前言
并發(fā)量限制因素 (五元組)
準(zhǔn)備
測(cè)試并發(fā)量
思考局限
如何打破
超時(shí)連接問(wèn)題何在
connection timeout的解決辦法
實(shí)際開(kāi)發(fā)中的處理方案
文章小結(jié)
前言
本文純粹就是小杰學(xué)習(xí)后端服務(wù)器開(kāi)發(fā)的一個(gè)學(xué)習(xí)筆記系列.
小杰會(huì)盡量地將其梳理清楚, 大家一起學(xué)習(xí),共同進(jìn)步, 知識(shí)不分高低, 計(jì)算機(jī)的學(xué)習(xí)小杰認(rèn)為也是一個(gè) ? ?量變 ? ---> ? 質(zhì)變 ? ?的過(guò)程
天道酬勤, 水滴石穿, 在不同的階段就干好自己當(dāng)前階段力所能及之事, ?至少是沒(méi)有在寢室的床上癱著消磨時(shí)光 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -------- ? 愿大家都學(xué)有所成,所獲
并發(fā)量限制因素 (五元組)
五元組: (srcip, dstip, srcport, dstport, proto)?
- 文件句柄, 文件描述符數(shù)量? open files
- 系統(tǒng)內(nèi)存限制?
- 端口數(shù)量限制
- 網(wǎng)絡(luò)帶寬的限制 (一般不做考慮)
- 數(shù)據(jù)庫(kù)的并發(fā)量限制
準(zhǔn)備
先將 open files 修改到 100W的上限
查看單個(gè)進(jìn)程可以打開(kāi)的文件句柄的數(shù)目, open files的大小
命令:ulimit?
ulimit -a 顯示當(dāng)前所有的資源限制
ulimit -H 設(shè)置硬件資源限制
ulimit -S 設(shè)置軟件資源限制
ulimit -n 設(shè)置進(jìn)程最大打開(kāi)文件描述符數(shù)
我的已經(jīng)被我自己修改為了100W的量級(jí)了?
修改方式:
- 命令修改?: ulimit -n? <value>? ? 缺陷:不是永久修改, 不涉及寫(xiě)磁盤(pán), 重啟shell之后修改消失
- 修改配置文件??limits.conf 文件限制著用戶可以使用的最大文件數(shù),最大線程,最大內(nèi)存等資源使用量。??? vim /etc/security/limits.conf? ? ? ? 涉及寫(xiě)磁盤(pán), 每一次登錄shell都會(huì)加載配置文件, 永久修改? ? ? ? ?配置文件記憶技巧, 資源使用限制涉及到系統(tǒng)安全, 故而在security中
測(cè)試并發(fā)量
首先針對(duì)之前寫(xiě)的reactor進(jìn)行一個(gè)并發(fā)量的測(cè)試.
測(cè)試代碼server代碼在上一篇文章中:
epoll高度封裝reactor,幾乎所有可見(jiàn)服務(wù)器的底層框架_小杰312的博客-CSDN博客_小杰框架epoll高度封裝reactor,幾乎所有可見(jiàn)服務(wù)器的底層框架https://blog.csdn.net/weixin_53695360/article/details/123894158?spm=1001.2014.3001.5502
并發(fā)量:服務(wù)器可以承載的客戶端的連接數(shù)量, 也就是可以維護(hù)的 sockfd的數(shù)量.
客戶端并發(fā)量測(cè)試
初始版本測(cè)試結(jié)果如下:
思考局限
srcip 客戶端的ip是固定的 srcport 客戶端的可用端口數(shù)理論值是?65535
dstip +??dstport 固定, 服務(wù)器ip 跟 端口固定
?此時(shí)并發(fā)量可以達(dá)到2.8W, 然后報(bào)錯(cuò)不能分配地址了. 其實(shí)是客戶端端口分配上限了.
如何打破
增加服務(wù)器端口數(shù),? 此時(shí)完全先從五元組確定唯一連接的方向切入,思考出可以增加服務(wù)端的監(jiān)視窗口數(shù)量來(lái)提高并發(fā)量, 打破限制.? ? ? ?(多端口, 多窗口監(jiān)視,有效提升客戶接入量)
- 將服務(wù)器的端口數(shù)開(kāi)啟到100個(gè)
- 核心改變代碼:
創(chuàng)建監(jiān)視端口 init_listen_sock
int init_sock(short port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
err_exit("socket");
}
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
if (-1 == bind(sockfd, (SA*)&addr, sizeof(addr))) {
err_exit("bind");
}
if (-1 == listen(sockfd, 5)) {
err_exit("listen");
}
return sockfd;
}
//循環(huán)向eventloop中添加多監(jiān)視窗口, 多port
int i = 0;
for (i = 0; i < LISTEN_PORT; ++i) {
sockfd = init_sock(i + port);
//將其加入到event_loop中
struct epoll_event ev;
ev.events = EPOLLIN; //level 觸發(fā)
//注冊(cè)監(jiān)視事件
struct sockitem* si = (struct sockitem*)malloc(sizeof(struct sockitem));
si->sockfd = sockfd;
si->callback = accept_cb;//設(shè)置事件處理器
ev.data.ptr = si;
epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);
}
- 再測(cè)試
經(jīng)過(guò)漫長(zhǎng)等待之后它終于還是沒(méi)有達(dá)到100W左右, 而是killed了, 因?yàn)閮?nèi)存限制被killed了
其實(shí)這個(gè)不是正常現(xiàn)象,我這里是因?yàn)閮?nèi)存限制而產(chǎn)生了killed, 實(shí)際上,內(nèi)存限制沒(méi)有打破的情況下也還是無(wú)法達(dá)到100W, 會(huì)出現(xiàn)? ? connection timeout 連接超時(shí)的錯(cuò)誤?
超時(shí)連接問(wèn)題何在
100 * 2.8W? 服務(wù)端端口數(shù)(100端口)?* 客戶端端口數(shù) (隨機(jī)2.8W端口, 之前測(cè)試)?可以達(dá)到100W,所以接入量的限制不是五元組. 而是其他因素
針對(duì)connect的超時(shí)連接錯(cuò)誤, 我們透過(guò)TCP三次握手去看, 問(wèn)題在于服務(wù)端沒(méi)有向客戶端返回一個(gè)ACK, 導(dǎo)致了connect 超時(shí).? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?---?server 的ACK為何沒(méi)有到
此時(shí)的限制其實(shí)在于協(xié)議棧了. iptables, 一種過(guò)濾裝置? 防火墻
connection timeout的解決辦法
修改??/etc/sysctl.conf?配置文件, 打破限制.
net.nf_conntrack_max 就是防火墻的限制
fs.file-max = 1048576
net.nf_conntrack_max = 1048576
net.ipv4.tcp_rmem =128 256 512
net.ipv4.tcp_wmem =128 256 512
至此其實(shí)可以完成百萬(wàn)接入量了, 只是我的服務(wù)器內(nèi)存是在太小, 無(wú)法達(dá)到要求, 內(nèi)存足夠是可以跑到100W的.
實(shí)際開(kāi)發(fā)中的處理方案
采用多進(jìn)程的方式, 而不是多端口的方式.文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-702935.html
測(cè)試代碼:? ?MAX_PORT : 代表的是端口數(shù), 與服務(wù)器端口數(shù)保持一致文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-702935.html
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#define MAX_BUFFER 128
#define MAX_EPOLLSIZE (384*1024)
#define MAX_PORT 1
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int isContinue = 0;
static int ntySetNonblock(int fd) {
int flags;
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return flags;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) return -1;
return 0;
}
// s設(shè)置好地址可復(fù)用
static int ntySetReUseAddr(int fd) {
int reuse = 1;
return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}
int main(int argc, char **argv) {
if (argc <= 2) {
printf("Usage: %s ip port\n", argv[0]);
exit(0);
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int connections = 0;
char buffer[128] = {0};
int i = 0, index = 0;
struct epoll_event events[MAX_EPOLLSIZE];
int epoll_fd = epoll_create(MAX_EPOLLSIZE);
strcpy(buffer, " Data From MulClient\n");
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 tv_begin;
gettimeofday(&tv_begin, NULL);
while (1) {
if (++index >= MAX_PORT) index = 0;
struct epoll_event ev;
int sockfd = 0;
if (connections < 340000 && !isContinue) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
goto err;
}
//ntySetReUseAddr(sockfd);
addr.sin_port = htons(port+index);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
goto err;
}
ntySetNonblock(sockfd);
ntySetReUseAddr(sockfd);
sprintf(buffer, "Hello Server: client --> %d\n", connections);
send(sockfd, buffer, strlen(buffer), 0);
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLOUT;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
connections ++;
}
//connections ++;
if (connections % 1000 == 999 || connections >= 340000) {
struct timeval tv_cur;
memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
gettimeofday(&tv_begin, NULL);
//計(jì)算出時(shí)間
int time_used = TIME_SUB_MS(tv_begin, tv_cur);
printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
//每一次僅僅只是拿出來(lái)
int nfds = epoll_wait(epoll_fd, events, connections, 100);
for (i = 0;i < nfds;i ++) {
int clientfd = events[i].data.fd;
if (events[i].events & EPOLLOUT) {
sprintf(buffer, "data from %d\n", clientfd);
send(sockfd, buffer, strlen(buffer), 0);
} else if (events[i].events & EPOLLIN) {
char rBuffer[MAX_BUFFER] = {0};
ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
if (length > 0) {
printf(" RecvBuffer:%s\n", rBuffer);
if (!strcmp(rBuffer, "quit")) {
isContinue = 0;
}
} else if (length == 0) {
printf(" Disconnect clientfd:%d\n", clientfd);
connections --;
close(clientfd);
} else {
if (errno == EINTR) continue;
printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
close(clientfd);
}
} else {
printf(" clientfd:%d, errno:%d\n", clientfd, errno);
close(clientfd);
}
}
}
usleep(1 * 1000);
}
return 0;
err:
printf("error : %s\n", strerror(errno));
return 0;
}
文章小結(jié)
- 做并發(fā)測(cè)試時(shí)候出現(xiàn)了問(wèn)題,我們思考的方式是: 內(nèi)存限制,? open files 文件句柄數(shù)限制,? 五元組組合限制, 網(wǎng)絡(luò)帶寬, 數(shù)據(jù)庫(kù)... 方向入手思考
- 對(duì)于各種系統(tǒng)資源的限制, 可以通過(guò)修改配置文件的方式做出永久修改? ?/etc/security/limits.conf? ? ? ?+? ? ?/etc/sysctl.conf??
- 五元組組合限制? ? ? 防火墻限制
- ulimit -a 查看所有的資源限制, free -h 查看內(nèi)存限制, htop 動(dòng)態(tài)觀察CPU? + 內(nèi)存占用情況, 便于分析異常
到了這里,關(guān)于高并發(fā)服務(wù)器的限制有哪些,如何提高并發(fā)量的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!