免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 18618 | 回复: 8

【已解决】大并发下listen的连接完成对列backlog太小导致客户超时,服务器效率低下 [复制链接]

论坛徽章:
0
发表于 2011-05-12 20:18 |显示全部楼层
本帖最后由 yayu_myself 于 2011-05-15 00:01 编辑

服务器代码就是之前论坛发过的一个epoll代码,根据需要改了就是服务端send的参数。。如下:
  1. /*-------------------------------------------------------------------------------------------------
  2. gcc -o httpd httpd.c -lpthread
  3. author: wyezl
  4. 2006.4.28
  5. ---------------------------------------------------------------------------------------------------*/

  6. #include <sys/socket.h>
  7. #include <sys/epoll.h>
  8. #include <netinet/in.h>
  9. #include <arpa/inet.h>
  10. #include <fcntl.h>
  11. #include <unistd.h>
  12. #include <stdio.h>
  13. #include <pthread.h>
  14. #include <errno.h>
  15. #include <string.h>
  16. #include <stdlib.h>

  17. #define PORT 8888
  18. #define MAXFDS 5000
  19. #define EVENTSIZE 100

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

  21. int epfd;
  22. void *serv_epoll(void *p);
  23. void setnonblocking(int fd)
  24. {
  25.     int opts;
  26.     opts=fcntl(fd, F_GETFL);
  27.     if (opts < 0)
  28.     {
  29.           fprintf(stderr, "fcntl failed\n");
  30.           return;
  31.     }
  32.     opts = opts | O_NONBLOCK;
  33.     if(fcntl(fd, F_SETFL, opts) < 0)
  34.     {
  35.           fprintf(stderr, "fcntl failed\n");
  36.           return;
  37.     }
  38.     return;
  39. }

  40. int main(int argc, char *argv[])
  41. {
  42.     int fd, cfd,opt=1;
  43.     struct epoll_event ev;
  44.     struct sockaddr_in sin, cin;
  45.     socklen_t sin_len = sizeof(struct sockaddr_in);
  46.     pthread_t tid;
  47.     pthread_attr_t attr;

  48.     epfd = epoll_create(MAXFDS);
  49.     if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0)
  50.     {
  51.           fprintf(stderr, "socket failed\n");
  52.           return -1;
  53.     }
  54.     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));

  55.     memset(&sin, 0, sizeof(struct sockaddr_in));
  56.     sin.sin_family = AF_INET;
  57.     sin.sin_port = htons((short)(PORT));
  58.     sin.sin_addr.s_addr = INADDR_ANY;
  59.     if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0)
  60.     {
  61.           fprintf(stderr, "bind failed\n");
  62.           return -1;
  63.     }
  64.     if (listen(fd, 32) != 0)
  65.     {
  66.           fprintf(stderr, "listen failed\n");
  67.           return -1;
  68.     }

  69.     pthread_attr_init(&attr);
  70.     pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
  71.     if (pthread_create(&tid, &attr, serv_epoll, NULL) != 0)
  72.     {
  73.           fprintf(stderr, "pthread_create failed\n");
  74.           return -1;
  75.     }

  76.     while ((cfd = accept(fd, (struct sockaddr *)&cin, &sin_len)) > 0)
  77.     {
  78.           setnonblocking(cfd);
  79.           ev.data.fd = cfd;
  80.           ev.events = EPOLLIN | EPOLLET |  EPOLLERR | EPOLLHUP | EPOLLPRI;
  81.           epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
  82.           //printf("connect from %s\n",inet_ntoa(cin.sin_addr));
  83.           //printf("cfd=%d\n",cfd);
  84.     }

  85.     if (fd > 0)
  86.           close(fd);
  87.     return 0;
  88. }

  89. void *serv_epoll(void *p)
  90. {
  91.     int i, ret, cfd, nfds;;
  92.     struct epoll_event ev,events[EVENTSIZE];
  93.     char buffer[512];

  94.     while (1)
  95.     {
  96.           nfds = epoll_wait(epfd, events, EVENTSIZE , -1);
  97.           //printf("nfds ........... %d\n",nfds);
  98.           for (i=0; i<nfds; i++)
  99.           {
  100.                 if(events[i].events & EPOLLIN)
  101.                 {
  102.                     cfd = events[i].data.fd;
  103.                     ret = recv(cfd, buffer, sizeof(buffer),0);
  104.                     //printf("read ret..........= %d\n",ret);

  105.                     ev.data.fd = cfd;
  106.                     ev.events = EPOLLOUT | EPOLLET;
  107.                     epoll_ctl(epfd, EPOLL_CTL_MOD, cfd, &ev);
  108.                 }
  109.                 else if(events[i].events & EPOLLOUT)
  110.                 {
  111.                     cfd = events[i].data.fd;
  112.                     // send第三个参数改为客户请求的字节数
  113.                     ret = send(cfd, BUFFER, atoi(buffer), 0);
  114.                     //printf("send ret...........= %d\n", ret);

  115.                     ev.data.fd = cfd;
  116.                     epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);
  117.                   
  118.                     close(cfd);

  119.                 }

  120.                 else
  121.                 {      
  122.                     cfd = events[i].data.fd;
  123.                     ev.data.fd = cfd;
  124.                     epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);
  125.                     close(cfd);
  126.                 }


  127.           }
  128.     }
  129.     return NULL;
  130. }
复制代码
客户的测试代码就是<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中的引用
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
$ /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

论坛徽章:
0
发表于 2011-05-12 20:30 |显示全部楼层
setrlimit
改下打开文件数限制

论坛徽章:
0
发表于 2011-05-13 16:43 |显示全部楼层
本帖最后由 yayu_myself 于 2011-05-14 23:34 编辑

发现原因了

原来是listen的第二个参数backlog完成对列太小的原因!!!!

上面那个服务器的队列大小才32!!!!

线程池那个有1024!!!当然这个1024受限于系统的net.core.somaxconn,默认是128的,但总比32大。。同时有300个线程,能很快从accept取得已完成的连接。

试想下1W个并发,connect的时候,队列满了之后,服务器就不理会客户的connect,客户只能再次尝试,如果碰巧这时候队列还是满的。。那么就再次尝试,如果命真的那么差。。估计挂掉之前都连不上,就超时了!!!!

这个问题整了我一天了!!!

net.core.somaxconn的大小要比较大,否则listen的backlog再大也会被改变为这个值!!!

详细的在最上面的帖子已经说了。。

论坛徽章:
0
发表于 2011-05-14 10:45 |显示全部楼层
nice

用google搜自己的帖子就是了

论坛徽章:
0
发表于 2011-05-14 23:49 |显示全部楼层
nice

用google搜自己的帖子就是了
wenjianhn 发表于 2011-05-14 10:45


呵呵。。现在知道在上面那行点进空间,然后里面就有。。

论坛徽章:
0
发表于 2011-05-14 23:57 |显示全部楼层
300个线程??? 一般CPU*2个数就够了,线程太多,CPU切换忙死了。

论坛徽章:
0
发表于 2011-05-14 23:59 |显示全部楼层
300个线程??? 一般CPU*2个数就够了,线程太多,CPU切换忙死了。
anthony1983 发表于 2011-05-14 23:57


呵呵。。这个只是测试。。我是新手。。具体以后实际中怎么应用。还要多测试。。学习。。

论坛徽章:
0
发表于 2011-05-15 00:03 |显示全部楼层
还有,如果是客户端和服务器都在一台机器上,做上述测试的话,不一定准的。

论坛徽章:
0
发表于 2011-05-15 12:58 |显示全部楼层
还有,如果是客户端和服务器都在一台机器上,做上述测试的话,不一定准的。
anthony1983 发表于 2011-05-15 00:03


嗯。。这个还有待继续测试。。只是当时刚测试的时候,就碰到差距这么大。。导致了一直在查找问题。。以后还希望多指教!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

DTCC2020中国数据库技术大会 限时8.5折

【架构革新 高效可控】2020年8月17日~19日第十一届中国数据库技术大会将在北京隆重召开。

大会设置2大主会场,20+技术专场,将邀请超百位行业专家,重点围绕数据架构、AI与大数据、传统企业数据库实践和国产开源数据库等内容展开分享和探讨,为广大数据领域从业人士提供一场年度盛会和交流平台。

http://dtcc.it168.com


大会官网>>
  

北京盛拓优讯信息技术有限公司. 版权所有 16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122
中国互联网协会会员  联系我们:huangweiwei@it168.com
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP