免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: GNM
打印 上一主题 下一主题

关于nginx/lighttpd epoll高并发的疑惑 [复制链接]

论坛徽章:
0
11 [报告]
发表于 2009-06-08 15:20 |只看该作者

回复 #10 bobozhang 的帖子

可能我上面没有表达清楚,  对于accept 进来的连接请求,及 fd 读/写等状态信息的维护epoll是可以很高效的处理,并能达到好几万。。但这么多连接请求,都是有相应任务的,如请求get index.php 或下载一个200M 电影,是需要server端进行read \write 进行IO操作的。。我想表达的是,如果采用单进程串行处理所有任务的话,那瓶颈不在于连接请求维护上,而在于任务处理延时非常大的read 和write上,或者某一任务有个user/pass登陆过程?那其它后续连接请求的任务不都堆积了吗??    不知表述清楚没

论坛徽章:
0
12 [报告]
发表于 2009-06-08 15:31 |只看该作者
表达得很清楚,我没有看过lighttp的源代码,具体怎么实现的不清楚.但我觉得对于比较费时的处理我想应该是不必把这个任务处理完后再去处理其它任务,比如下载200M的电影,完全可以每次只给他传几十k数据(或则也许write()返回了EAGAIN)后马上转去服务其它连接,其他连接处理后(也许这些其它的连接也没有一次处理完,不过处理到哪了这些状态lighttp是可以记录的)再回来处理前面没有处理完的那些任务.

论坛徽章:
0
13 [报告]
发表于 2009-06-08 15:38 |只看该作者
原帖由 GNM 于 2009-6-8 14:56 发表
如果这样的话,nginx还是会用到类似线程池技术罗??  之前粗略看了看lighttpd源码结构,没有用到线程池,一般情况是单进程,考虑多CPU情况也可以fork几个worker进程,但不像apache好几百的进程摆在那


nginx 肯定是有线程池的啊
lighttpd不清楚, 如果纯粹单进程轮循无论怎么优化, 对于复杂的web应用效果都是不好的... 尽管全部无阻赛IO, IO可以切割, 但别的逻辑业务并不是都可以方便切片的. 好比说, 我的 web 是个搜索功能, 由第三方提供封装好的API, 一次搜索时间可能较长, 这中间也不方便切片, 如果正好在时间长的调用上, 那其它客户连接必须全部等候, 这样子效率肯定升不上去的.

所以大部分人都把lighttpd架起来处理一些.js,.css,.html等静态文件, 对于 php 之类的, nginx/lighttpd都是采用fastcgi来操作的, 这样也就转化成IO了可以切片, 复杂的逻辑处理还是交给了第三方.


我现在自己做的一个server构架基本上也是类似的, 但有也不同的地方, 目的也是应对高并发, 复杂业务(搜索), 对于HTTP也类似:

每个server 由1个 master process +若干个 worker process 组成, 每个 worker 是单进程+一个线程池, master 不干嘛就监控worker, 意外死了就再生一个, 同时处理一些多进程共享数据的维护, 这一点和nginx一样.

当有请求进来各个worker公平抢先accept (反正不太考虑复杂多平台, 不上锁,看了某些资料上锁效率较差), accept 到了就在主线程先用调用epoll/kqueue监视fd, 当有数据进来时也由主线程统一先接收到缓冲区, 直到主线程认为该client已经满足一个基本的request了(比如http协议中已经读到空行了)这时主线程把该客户请求安排给工作线程池去处理, 它处理完这个请求又把这个连接交给主线程去监视..

说得比较笼统, 主要是这样做可能可以省下比较多的闲置进程/线程, 而不是一个客房连接进来马上就分配线程/进程去接待它.., 而且也能在worker主线程中充分发挥 epoll/kqueue 的机能.

[ 本帖最后由 hightman 于 2009-6-8 15:46 编辑 ]

论坛徽章:
0
14 [报告]
发表于 2009-06-08 16:03 |只看该作者
原帖由 GNM 于 2009-6-8 14:12 发表



连接和任务处理是两个概念,epoll即使能并发处理上万并发连接,但如果是单进程的话这么多连接的任务也处理不过来吧

这是肯定的啊,业务处理如果耗时很大,那肯定要并发了。

论坛徽章:
0
15 [报告]
发表于 2009-06-08 16:07 |只看该作者

回复 #13 hightman 的帖子

谢谢版主详尽解答,目前我也有个项目遇到类似高并发的需求,看来还得借助线程池,之前都是用select的,下来好好看看epoll,网上找了找,感觉以下这种模型也不错,和大家分享


一个echo server实现:


int main()

{

     int i, maxi, listenfd, connfd, sockfd,nfds;

     pthread_t tid1,tid2;

     

     struct task *new_task=NULL;

     struct user_data *rdata=NULL;

     socklen_t clilen;

     

     pthread_mutex_init(&mutex,NULL);

     pthread_cond_init(&cond1,NULL);

     //初始化用于读线程池的线程

     pthread_create(&tid1,NULL,readtask,NULL);

     pthread_create(&tid2,NULL,readtask,NULL);

     

     //生成用于处理accept的epoll专用的文件描述符   

     epfd=epoll_create(256);



     struct sockaddr_in clientaddr;

     struct sockaddr_in serveraddr;

     listenfd = socket(AF_INET, SOCK_STREAM, 0);

     //把socket设置为非阻塞方式

     setnonblocking(listenfd);

     //设置与要处理的事件相关的文件描述符

     ev.data.fd=listenfd;

     //设置要处理的事件类型

     ev.events=EPOLLIN|EPOLLET;

     //注册epoll事件

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

     

     bzero(&serveraddr, sizeof(serveraddr));

     serveraddr.sin_family = AF_INET;

     

     char *local_addr="200.200.200.222";

     inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);

     serveraddr.sin_port=htons(SERV_PORT);

     bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));

     listen(listenfd, LISTENQ);

     

     maxi = 0;

     for ( ; ; ) {

          //等待epoll事件的发生

          nfds=epoll_wait(epfd,events,20,500);

          //处理所发生的所有事件      

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

        {

               if(events.data.fd==listenfd)

               {

                    

                    connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);

                    if(connfd<0){

                      perror("connfd<0";

                      exit(1);

                   }

                    setnonblocking(connfd);

                    

                    char *str = inet_ntoa(clientaddr.sin_addr);

                    std::cout<<"connec_ from >>"<<str<<std::endl;

                    //设置用于读操作的文件描述符

                    ev.data.fd=connfd;

                    //设置用于注测的读操作事件

                 ev.events=EPOLLIN|EPOLLET;

                    //注册ev

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

               }

            else if(events.events&EPOLLIN)

            {

                    printf("reading!\n";                 

                    if ( (sockfd = events.data.fd) < 0) continue;

                    new_task=new task();

                    new_task->fd=sockfd;

                    new_task->next=NULL;

                    //添加新的读任务

                    pthread_mutex_lock(&mutex);

                    if(readhead==NULL)

                    {

                      readhead=new_task;

                      readtail=new_task;

                    }   

                    else

                    {   

                     readtail->next=new_task;

                      readtail=new_task;

                    }   

                   //唤醒所有等待cond1条件的线程

                    pthread_cond_broadcast(&cond1);

                    pthread_mutex_unlock(&mutex);  

              }

               else if(events.events&EPOLLOUT)

               {   

              rdata=(struct user_data *)events.data.ptr;

                 sockfd = rdata->fd;

                 write(sockfd, rdata->line, rdata->n_size);

                 delete rdata;

                 //设置用于读操作的文件描述符

                 ev.data.fd=sockfd;

                 //设置用于注测的读操作事件

               ev.events=EPOLLIN|EPOLLET;

                 //修改sockfd上要处理的事件为EPOLIN

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

               }

                              

          }

         

     }

}

void * readtask(void *args)

{

   

   int fd=-1;

   unsigned int n;

   //用于把读出来的数据传递出去

   struct user_data *data = NULL;

   while(1){

        

        pthread_mutex_lock(&mutex);

        //等待到任务队列不为空

        while(readhead==NULL)

             pthread_cond_wait(&cond1,&mutex);

        

        fd=readhead->fd;

        //从任务队列取出一个读任务

        struct task *tmp=readhead;

        readhead = readhead->next;

        delete tmp;

        pthread_mutex_unlock(&mutex);

        data = new user_data();

        data->fd=fd;

        if ( (n = read(fd, data->line, MAXLINE)) < 0) {

           

           if (errno == ECONNRESET) {

             close(fd);

            

          } else

             std::cout<<"readline error"<<std::endl;

           if(data!=NULL)delete data;

        } else if (n == 0) {

            close(fd);

           printf("Client close connect!\n";

           if(data!=NULL)delete data;

        } else{

        

        data->n_size=n;

        //设置需要传递出去的数据

        ev.data.ptr=data;

        //设置用于注测的写操作事件

        ev.events=EPOLLOUT|EPOLLET;

        //修改sockfd上要处理的事件为EPOLLOUT

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

       }

   }

}

论坛徽章:
0
16 [报告]
发表于 2009-06-08 18:03 |只看该作者
这个问题我前几天就问过了


io 的瓶颈 和你说的cpu 瓶颈是2个方面, 单thread cpu 可能成为瓶竟 ,  但是多thread 带来的好处 应该和业务相关考虑,如果业务相关性不高, thread 肯定能带来好处,如果相关性很高, 带来问题回很多。

论坛徽章:
0
17 [报告]
发表于 2009-06-08 18:16 |只看该作者
lighttpd把所有可能阻塞的操作, 都委托给单独的进程或者线程中. 例如, 通过fastcgi接口, 把请求转发给fastcgi处理器进程(如PHP), 并从fastcgi接口读取响应. 这样, lighttpd又可以使用单进程io复用和fastcgi处理器进程交互. 也就是, 把所有可能阻塞的操作请求委托出去, 把委托的处理者(如PHP进程)当成客户端一样来对待交互.

应该和 hightman 朋友说的类似, 注意委托这个概念.

[ 本帖最后由 ideawu 于 2009-6-8 18:23 编辑 ]

论坛徽章:
0
18 [报告]
发表于 2009-06-08 18:20 |只看该作者
对于读取200M文件这个例子, lighttpd通过sendfile接口, 把这个请求委托给操作系统内核处理.

论坛徽章:
0
19 [报告]
发表于 2009-06-08 19:30 |只看该作者
原帖由 ideawu 于 2009-6-8 18:16 发表
lighttpd把所有可能阻塞的操作, 都委托给单独的进程或者线程中. 例如, 通过fastcgi接口, 把请求转发给fastcgi处理器进程(如PHP), 并从fastcgi接口读取响应. 这样, lighttpd又可以使用单进程io复用和fastcgi处理 ...


关键是lighttpd普通模式是epoll+单进程,先不说php,就单静态页面、图片(<1M),2万并发连接请求任务也够处理半天了吧? 按版主上面的说法,延迟也会很严重。。所以这个模型瓶颈还是在单进程串行IO处理能上上(read/write)

[ 本帖最后由 GNM 于 2009-6-8 20:15 编辑 ]

论坛徽章:
0
20 [报告]
发表于 2009-06-08 22:08 |只看该作者
原帖由 GNM 于 2009-6-8 19:30 发表


关键是lighttpd普通模式是epoll+单进程,先不说php,就单静态页面、图片(

如果真是20k个请求每秒, 每个请求需要返回1M数据, 这已经是带宽瓶颈问题了, 延时是必然的, 使用多线程/多进程也会延时. 我希望你再次详细描述下你的问题, 也许我理解错了.

[ 本帖最后由 ideawu 于 2009-6-8 22:13 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP