免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 11035 | 回复: 14
打印 上一主题 下一主题

[C++] SPProcPool: Unix/Linux 上的进程池服务器框架(增加类似 apache 的 worker模型) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-12-09 11:25 |只看该作者 |倒序浏览
SPProcPool 是一个 linux/unix 平台上的进程池服务器框架,使用 c++ 实现。主要包含了几种不同类型的进程池的实现:
一个基于 Leader/Follower 模式的服务器端进程池(类似 apache 的 prefork 模型);
一个基于文件句柄传递的服务器端进程池;
一个用于非服务器端的,能够在多线程或事件驱动环境下使用的进程池。

主页:http://code.google.com/p/spprocpool/
下载:spprocpool

关于进程池服务器框架,在《unix网络编程(第二版)》的第 27 章,详细地描述了多种不同的实现方式。
基于 Leader/Follower 模式的实现:27.6 TCP 预先派生子进程服务器程序,accept 无上锁保护
基于文件句柄传递:27.9 TCP 预先派生子进程服务器程序,传递描述字

关于非服务器端的,能够在多线程或事件驱动环境下使用的进程池,这里做一个比较详细的说明。

多线程的好处是各个线程能够共享一个地址空间,因此对一些需要全局排序来调度的任务,使用多线程可以比较方便地实现。比如类似 postfix/qmgr 的模块,如果使用多线程的话,那么所有的邮件能够在一个优先队列中排队,按队列顺序进行投递;如果投递失败了,那么重新插入队列。
但另一方面,如果具体的任务处理部分已经有了实现,但不是线程安全的,这种问题要怎么来解决呢?

一个最直观的解决方法是每个任务直接 fork 一次。但是这种做法有几个细节问题需要认真考虑:
1.在子进程中如何把结果返回给调度进程?常见的方法是使用 pipe
2.每个任务 fork 一次,估计很多人的第一反应就是性能会怎么样?
3.如果调度进程中,除了负责 fork 的线程,还有其他的线程存在,那么就存在 fork-with-thread 的问题。
>>具体的内容可以参考:http://www.opengroup.org/onlinepubs/000095399/functions/fork.html

针对上面存在的问题,在每个任务直接 fork 一次的基础上,做了一些改进,就形成了这样的一个进程池实现:
1.在前面的方案中,存在调度进程(Sched)和工作进程(Worker)这两类进程。
>>为了避免 fork-with-thread 的问题,再增加一类管理进程(Manager)。管理进程的主要职责就是负责 fork 工作进程。
2.通常 Manager 是由 Sched fork 出来的,它们之间存在一条通信的 pipe (MgrPipe) 。
>>创建一个新的工作进程的流程如下:Sched 创建一个 pipe (WorkerPipe),把其中的一端用 send_fd 的方法发送给 Manager,
>>然后 Manager fork 一个 Worker 出来,并且把 WorkerPipe 传递给 Worker 。这样就在 Sched 和 Worker 之间建立了一个 Pipe 。
3.Worker 在被 fork 出来之后,通常就阻塞在读 WorkerPipe 上面。Sched 通过 WorkerPipe 发送任务给 Worker 。
>>Worker 完成任务之后,通过 WorkerPipe 发送结果给 Sched 。Worker 可以不断地重复这个过程,这样就达到了一个池的效果。
4.对于使用 libevent 这类事件驱动类型的程序,这个进程池也能方便地被调用。
>>因为 Worker 曝露出来的是一个 PipeFd,能够方便地加入到 libevent 的事件循环中。这类事件驱动类的程序,
>>通常使用单线程实现,当具体的任务处理可能需要耗费比较长时间的时候,就需要使用多线程或者多进程来辅助了。

[ 本帖最后由 iunknown 于 2008-1-4 21:25 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2007-12-11 18:57 |只看该作者
原帖由 iunknown 于 2007-12-9 11:25 发表
SPProcPool 是一个能够在多线程或事件驱动环境下使用的进程池实现。
主页:http://code.google.com/p/spprocpool/
下载:spprocpool

多线程的好处是各个线程能够共享一个地址空间,因此对一些需要全局排序 ...


没有实现类似之前的  SPServer 那样的一个框架吗?在目前的基础上,要实现类似 SPServer 那样的一个框架,应该不会很复杂了。
按目前的结构,继续使用 send_fd 的方式应该是最简单的做法。类似 UNP 的 27.9 TCP 预先派生子进程服务器程序,传递描述字。
不过 UNP 中提到这种方法的性能比起各个子进程直接 accept 差。

论坛徽章:
0
3 [报告]
发表于 2007-12-12 23:17 |只看该作者
原帖由 queue 于 2007-12-11 18:57 发表


没有实现类似之前的  SPServer 那样的一个框架吗?在目前的基础上,要实现类似 SPServer 那样的一个框架,应该不会很复杂了。
按目前的结构,继续使用 send_fd 的方式应该是最简单的做法。类似 UNP 的 27. ...



正好最近重新看了 UNP 的那几章。也计划实现一个多进程的 Server 框架。初步看了 apache 的做法,好复杂,还没看的出大概。
如果用 send_fd 的方式来做的话,的确比较简单,但是看 UNP 上的说法,慢的不是一点二点,是慢很多啊。
不过也可以试试看,可以对比一下,看相差有多远。

论坛徽章:
0
4 [报告]
发表于 2007-12-12 23:18 |只看该作者
先顶,后看。

论坛徽章:
0
5 [报告]
发表于 2007-12-14 14:36 |只看该作者
原帖由 iunknown 于 2007-12-12 23:17 发表



正好最近重新看了 UNP 的那几章。也计划实现一个多进程的 Server 框架。初步看了 apache 的做法,好复杂,还没看的出大概。
如果用 send_fd 的方式来做的话,的确比较简单,但是看 UNP 上的说法,慢的不 ...


上 google 做了一下查找,找到两份关于 prefork 的文章,写得很深入

Secure Pre-forking - A Pattern for Performance and Security
Multitasking server architectures

论坛徽章:
0
6 [报告]
发表于 2007-12-16 19:58 |只看该作者
原帖由 queue 于 2007-12-11 18:57 发表


没有实现类似之前的  SPServer 那样的一个框架吗?在目前的基础上,要实现类似 SPServer 那样的一个框架,应该不会很复杂了。
按目前的结构,继续使用 send_fd 的方式应该是最简单的做法。类似 UNP 的 27. ...



用 send_fd 的方法实现了一个 server 框架。
http://spprocpool.googlecode.com/files/spprocpool-0.2.src.tar.gz

为了与 UNP 27 章的其他类型 server 对比,实现了类似的一个测试案例 testinetserver 。
同时为了方便测试,把 UNP 的测试 client 也放入包里面了,改名为 testinetclient 。
UNP 书上列出的数据是指 server 端通过 getrusage 得到的时间统计,而不是 client 端统计到的响应时间。
为了更直观的观察,在 testinetclient  中加入了响应时间的统计。


  1. class SP_ProcUnpService : public SP_ProcInetService {
  2. public:
  3.   SP_ProcUnpService() {}
  4.   virtual ~SP_ProcUnpService() {}

  5.   virtual void handle( int sockfd ) {
  6.     int ntowrite;
  7.     ssize_t nread;
  8.     char line[MAXLINE], result[MAXN];

  9.     for ( ; ; ) {
  10.       if ( (nread = read(sockfd, line, MAXLINE)) == 0) {
  11.         return;   /* connection closed by other end */
  12.       }                 

  13.       /* line from client specifies #bytes to write back */
  14.       ntowrite = atol(line);
  15.       if ((ntowrite <= 0) || (ntowrite > MAXN)) {
  16.         syslog( LOG_WARNING, "WARN: client request for %d bytes", ntowrite);
  17.         exit( -1 );
  18.       }

  19.       SP_ProcPduUtils::writen(sockfd, result, ntowrite);
  20.     }
  21.   }
  22. };

  23. class SP_ProcUnpServiceFactory : public SP_ProcInetServiceFactory {
  24. public:
  25.   SP_ProcUnpServiceFactory() {}
  26.   virtual ~SP_ProcUnpServiceFactory() {}

  27.   virtual SP_ProcInetService * create() const {
  28.     return new SP_ProcUnpService();
  29.   }
  30. };

  31. void sig_int(int signo)
  32. {      
  33.   SP_ProcPduUtils::print_cpu_time();

  34.   kill( 0, SIGUSR1 );
  35. }   
  36.   
  37. int main( int argc, char * argv[] )
  38. {
  39.   SP_ProcInetServer server( "", 1770, new SP_ProcUnpServiceFactory() );

  40.   signal( SIGINT, sig_int );

  41.   server.runForever();

  42.   return 0;
  43. }
复制代码

[ 本帖最后由 iunknown 于 2007-12-16 19:59 编辑 ]

论坛徽章:
0
7 [报告]
发表于 2007-12-22 16:09 |只看该作者
原帖由 iunknown 于 2007-12-12 23:17 发表



正好最近重新看了 UNP 的那几章。也计划实现一个多进程的 Server 框架。初步看了 apache 的做法,好复杂,还没看的出大概。
如果用 send_fd 的方式来做的话,的确比较简单,但是看 UNP 上的说法,慢的不 ...



模仿 apache 实现了另外一个 TCP Preforked Server 框架。这个框架也可以认为是 Leader/Follower pattern 基于多进程的实现。
对比了传递 socket 句柄和  Leader/Follower 两种做法的性能,结果是 Leader/Follower 模型比传递 socket 句柄快。

采用的测试模型就是《Unix网络编程(第二版)》第27章的模型。对书上提到的 client 测试工具做了一下修改,加上了 client 端的时间测量。
测试的时候,两种框架都预先启动 10 个进程,并且 client 端也只跑 10 个进程,每个进程顺序发起 100 个请求,每次请求 512 字节。
针对每种框架,连续运行 client 10 次。每个 client 进程结束的时候,都输出它的总运行时间。最后要对比的就是进程的平均运行时间。

结果:
Leader/Follower       2635 / 100 = 26.35 (毫秒)
Descriptor Passing   3644 / 100 = 36.44 (毫秒)

简单来说,就是在 Leader/Follower 框架下,一个进程处理 100 个请求需要耗时 26 毫秒,而在另外一个框架下,需要 36 毫秒。

具体的测试程序可以参考:
spprocpool-0.3.src.tar.gz
Descriptor Passing
Leader/Follower

[ 本帖最后由 iunknown 于 2007-12-22 21:05 编辑 ]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
8 [报告]
发表于 2007-12-22 18:10 |只看该作者
好东西..有机会研究一下..

论坛徽章:
0
9 [报告]
发表于 2007-12-25 22:57 |只看该作者
原帖由 iunknown 于 2007-12-22 16:09 发表



模仿 apache 实现了另外一个 TCP Preforked Server 框架。这个框架也可以认为是 Leader/Follower pattern 基于多进程的实现。
对比了传递 socket 句柄和  Leader/Follower 两种做法的性能,结果是 Leade ...


在 parent 和 child 之间,完全使用 pipe 来同步,比较有新意。通常都会使用 filelock,或者 mutex,或者信号量来同步。
我想主要的好处是在于 child 意外崩溃的时候,parent 能够非常方便地检测出来。
子进程只要一退出,在 pipe 上 select 就会显示可读,然后读出来为 0 字节,就是子进程崩溃了。
但是在性能上会有不小的影响吧?

另外,在处理 MaxIdleProc 上,在 LF 模型中,好像不是严格精确的吧?
当 parent 发送 CHAR_EXIT 给 child 的时候,这个时候,如果 child 阻塞在 accept 中,那么 child 是不会退出的。

在处理 MaxIdleProc 上,最近刚刚看过的 tinyproxy 中的处理方法不错。
tinyproxy 是在 child_main 中处理的,这样就不会发生 child 阻塞在 accept 中的情况。


  1. static void
  2. child_main(struct child_s* ptr)
  3. {
  4. ......
  5.                 SERVER_COUNT_LOCK();
  6.                 if (*servers_waiting > child_config.maxspareservers) {
  7.                         /*
  8.                          * There are too many spare children, kill ourself
  9.                          * off.
  10.                          */
  11.                         log_message(LOG_NOTICE,
  12.                                     "Waiting servers (%d) exceeds MaxSpareServers (%d). Killing child.",
  13.                                     *servers_waiting, child_config.maxspareservers);
  14.                         SERVER_COUNT_UNLOCK();

  15.                         break;
  16.                 } else {
  17.                         SERVER_COUNT_UNLOCK();
  18.                 }

  19. ......
  20. }
复制代码

[ 本帖最后由 queue 于 2007-12-25 22:59 编辑 ]

论坛徽章:
0
10 [报告]
发表于 2007-12-26 09:55 |只看该作者
关注。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP