您的位置:首页 > 其它

大并发下listen的连接完成对列backlog太小导致客户超时,服务器效率低下

2015-07-01 18:14 330 查看
origin: http://blog.csdn.net/lizhi200404520/article/details/6981272
代码就是之前论坛发过的一个epoll代码,根据需要改了就是服务端send的参数。。如下:

/*-------------------------------------------------------------------------------------------------

gcc -o httpd httpd.c -lpthread

author: wyezl

2006.4.28

---------------------------------------------------------------------------------------------------*/

#include <sys/socket.h>

#include <sys/epoll.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <pthread.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

#define PORT 8888

#define MAXFDS 5000

#define EVENTSIZE 100

#define BUFFER "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\nContent-Type: text/html\r\n\r\nHello"

int epfd;

void *serv_epoll(void *p);

void setnonblocking(int fd)

{

int opts;

opts=fcntl(fd, F_GETFL);

if (opts < 0)

{

fprintf(stderr, "fcntl failed\n");

return;

}

opts = opts | O_NONBLOCK;

if(fcntl(fd, F_SETFL, opts) < 0)

{

fprintf(stderr, "fcntl failed\n");

return;

}

return;

}

int main(int argc, char *argv[])

{

int fd, cfd,opt=1;

struct epoll_event ev;

struct sockaddr_in sin, cin;

socklen_t sin_len = sizeof(struct sockaddr_in);

pthread_t tid;

pthread_attr_t attr;

epfd = epoll_create(MAXFDS);

if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0)

{

fprintf(stderr, "socket failed\n");

return -1;

}

setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));

memset(&sin, 0, sizeof(struct sockaddr_in));

sin.sin_family = AF_INET;

sin.sin_port = htons((short)(PORT));

sin.sin_addr.s_addr = INADDR_ANY;

if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0)

{

fprintf(stderr, "bind failed\n");

return -1;

}

if (listen(fd, 32) != 0)

{

fprintf(stderr, "listen failed\n");

return -1;

}

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

if (pthread_create(&tid, &attr, serv_epoll, NULL) != 0)

{

fprintf(stderr, "pthread_create failed\n");

return -1;

}

while ((cfd = accept(fd, (struct sockaddr *)&cin, &sin_len)) > 0)

{

setnonblocking(cfd);

ev.data.fd = cfd;

ev.events = EPOLLIN | EPOLLET | EPOLLERR | EPOLLHUP | EPOLLPRI;

epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);

//printf("connect from %s\n",inet_ntoa(cin.sin_addr));

//printf("cfd=%d\n",cfd);

}

if (fd > 0)

close(fd);

return 0;

}

void *serv_epoll(void *p)

{

int i, ret, cfd, nfds;;

struct epoll_event ev,events[EVENTSIZE];

char buffer[512];

while (1)

{

nfds = epoll_wait(epfd, events, EVENTSIZE , -1);

//printf("nfds ........... %d\n",nfds);

for (i=0; i<nfds; i++)

{

if(events[i].events & EPOLLIN)

{

cfd = events[i].data.fd;

ret = recv(cfd, buffer, sizeof(buffer),0);

//printf("read ret..........= %d\n",ret);

ev.data.fd = cfd;

ev.events = EPOLLOUT | EPOLLET;

epoll_ctl(epfd, EPOLL_CTL_MOD, cfd, &ev);

}

else if(events[i].events & EPOLLOUT)

{

cfd = events[i].data.fd;

// send第三个参数改为客户请求的字节数

ret = send(cfd, BUFFER, atoi(buffer), 0);

//printf("send ret...........= %d\n", ret);

ev.data.fd = cfd;

epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);



close(cfd);

}

else

{

cfd = events[i].data.fd;

ev.data.fd = cfd;

epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);

close(cfd);

}

}

}

return NULL;

}

复制代码
客户的测试代码就是<Unix 网络编程>中30章的那个client代码。

问题表现就是epoll这个的服务端对10000的并发请求处理特别慢,甚至还出现很多客户连接超时的情况!但是顺序的一个个请求没有问题。

测试如下:

首先是1个进程,顺序10000个请求。。服务端没问题,很快速完成。

然后是10000个进程,每个进程1个请求,前面都还正常,可是过一会服务端accept就阻塞了,大概有1-2s,之后又返回,有时候还会出现客户端连接超时的问题,但是这样测30章那个线程池(300个线程)的服务端代码,不管怎么测都不会有问题。

按理说accept应该能一直返回才对呀,为什么中途会阻塞呢?是内核参数问题?

之前也试过把listenfd也添加到epoll里,listenfd不是ET模式。。也有这样的问题。。

分析了很多可能:

 epoll本身处理效率的问题(这个自己都不信)

 服务端完成客户的处理请求太耗时,导致没有时间让accept返回其他客户连接(这个最简的单处理,应该也不会)

 单台机器测试,所以产生了太多的TIME_WAIT导致客户无法连接导致超时(之前以为是这个原因)

 内核的一些限制问题,服务端不能同时处理太多连接(可能的原因)

最终才发现真正原因!!!

原来上面这个服务器代码listen指定的backlog连接完成队列参数太小,只有32,导致高并发的时候,服务器的连接完成队列在极短的时间内被填满了,而accept的处理速度跟不上队列填满的速度,导致队列始终是满的,然后就不理会客户的其他连接请求,导致了客户connect超时,并且处理效率低下。

而线程池的backlog有1024,不过受限于内核参数的默认值最大128,所以线程池这个的backlog实际是128(见man listen),再加上300个线程,每个线程独自accpet,所以能很快从完成队列中取得连接,客户的connect也不会超时了,如果把线程数改为1个,客户连接也会超时。

下面是man listen中的引用

QUOTE:
If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is

128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128.

详细信息可以man listen。同时man tcp里面有很多限制对服务器来说需要改的。

网上看到的一个修改服务器参数的:http://hi.baidu.com/yupanlovehlq
... cc2155faf2c099.html

QUOTE:
$ /proc/sys/net/core/wmem_max

最大socket写buffer,可参考的优化值:873200

$ /proc/sys/net/core/rmem_max

最大socket读buffer,可参考的优化值:873200

$ /proc/sys/net/ipv4/tcp_wmem

TCP写buffer,可参考的优化值: 8192 436600 873200

$ /proc/sys/net/ipv4/tcp_rmem

TCP读buffer,可参考的优化值: 32768 436600 873200

$ /proc/sys/net/ipv4/tcp_mem

同样有3个值,意思是:

net.ipv4.tcp_mem[0]:低于此值,TCP没有内存压力.

net.ipv4.tcp_mem[1]:在此值下,进入内存压力阶段.

net.ipv4.tcp_mem[2]:高于此值,TCP拒绝分配socket.

上述内存单位是页,而不是字节.可参考的优化值是:786432 1048576 1572864

$ /proc/sys/net/core/netdev_max_backlog

进入包的最大设备队列.默认是300,对重负载服务器而言,该值太低,可调整到1000.

$ /proc/sys/net/core/somaxconn

listen()的默认参数,挂起请求的最大数量.默认是128.对繁忙的服务器,增加该值有助于网络性能.可调整到256.

$ /proc/sys/net/core/optmem_max

socket buffer的最大初始化值,默认10K.

$ /proc/sys/net/ipv4/tcp_max_syn_backlog

进入SYN包的最大请求队列.默认1024.对重负载服务器,增加该值显然有好处.可调整到2048.

$ /proc/sys/net/ipv4/tcp_retries2

TCP失败重传次数,默认值15,意味着重传15次才彻底放弃.可减少到5,以尽早释放内核资源.

$ /proc/sys/net/ipv4/tcp_keepalive_time

$ /proc/sys/net/ipv4/tcp_keepalive_intvl

$ /proc/sys/net/ipv4/tcp_keepalive_probes

这3个参数与TCP KeepAlive有关.默认值是:

tcp_keepalive_time = 7200 seconds (2 hours)

tcp_keepalive_probes = 9

tcp_keepalive_intvl = 75 seconds

意思是如果某个TCP连接在idle 2个小时后,内核才发起probe.如果probe 9次(每次75秒)不成功,内核才彻底放弃,认为该连接已失效.对服务器而言,显然上述值太大. 可调整到:

/proc/sys/net/ipv4/tcp_keepalive_time 1800

/proc/sys/net/ipv4/tcp_keepalive_intvl 30

/proc/sys/net/ipv4/tcp_keepalive_probes 3

$ proc/sys/net/ipv4/ip_local_port_range

指定端口范围的一个配置,默认是32768 61000,已够大.

net.ipv4.tcp_syncookies = 1

表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1

表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1

表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout = 30

表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。

net.ipv4.tcp_keepalive_time = 1200

表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。

net.ipv4.ip_local_port_range = 1024 65000

表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。

net.ipv4.tcp_max_syn_backlog = 8192

表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。

net.ipv4.tcp_max_tw_buckets = 5000

表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为 5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。

一般设置:

1 sudo vi /etc/sysctl.conf

在最下面编辑添加:

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 1200

net.ipv4.route.gc_timeout = 100

net.ipv4.ip_local_port_range = 1024 65000

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_syn_retries = 1

net.ipv4.tcp_synack_retries = 1

net.ipv4.tcp_max_syn_backlog = 262144

net.core.netdev_max_backlog = 262144

net.core.somaxconn = 262144

net.ipv4.tcp_mem = 94500000 915000000 927000000

保存退出

2 sudo /sbin/sysctl -p
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: