首页 > C/C++ > Apache MPM Prefork设计方法浅析

Apache MPM Prefork设计方法浅析

2011年5月16日 发表评论 阅读评论 9,419 人阅读过  

最近几天翻阅了apache的MPM(Multi-Processing Module)机制相关的代码,虽然还有很多细节没有搞明白,但对apache的服务器模型有了一个大体的概念,对于不同的操作系统,apache提供了不同的默认MPM模型,下表是不同操作系统默认的MPM模型:

BeOS beos
Netware mpm_netware
OS/2 mpmt_os2
Unix prefork
Windows mpm_winnt

Unix平台则对应着prefork模型,prefork从名字上看意思是预先生成子进程,所以这种模型大致上是怎么工作的我们心里差不多有些认识了,prefork是一种很重要的服务器程序设计模型,对应的还有prethread,prefork一般应用在Unix平台上,因为在服务器启动时需要预告fork出一些空闲的子进程,由它们共同监听客户端的请求,这样来实现快速高并发的特性,这种机制之所以不适合Windows等平台,是因为在Windows等平台上进程的代价太高。

apache的进程管理中有一个叫做scoreboard(记分牌)的概念,主进程在进入MPM循环以前会先在进程池中创建一个scoreboard对象,该对象定义如下:

typedef struct {
    global_score *global;
    process_score *parent;
    worker_score **servers;
    lb_score     *balancers;
} scoreboard;

global_score保存主进程的状态,process_score则是一个数组插槽,每个插槽保存一个子进程的状态,worker_score则是一个二维数组插槽,用来保存每个子进程创建的线程状态,根据这个结构主进程可以对子进程以及相关的线程进行管理,apache按照最大化的原则来分配内存,比如会按配置中允许最多的进程数目来为parent分配内存空间。

MPM初始化还有一个很重要的方面是创建一个进程锁,fork()出来的子进程与父进程并不共享内存空间,多进程之于多线程的优势在于多进程可以省去多线程进行线程同步的开销,而这里创建的进程锁,主要作用是为了给accept()加锁,为了避免thundering herd问题。apache实现了五种类型的进程锁,使用flock()或fcntl()实现的文件锁,Posix信号量或System V信号量,以及使用pthread线程库实现的互斥锁。我理解的是文件锁的效率会低于其它类型的锁,因为文件锁要涉及到文件系统的IO操作。我只阅读了跟pthread相关的代码,和一般的多进程程序实现方式一致,因为子进程与父进程以及子进程之间不共享内存空间的,所以不可能像多线程程序一样将互斥锁定义为全局变量 ,因此使用共享内存机制,将互斥锁变量存放到共享内存里面,并设置共享属性。然后便可以使用该互斥锁对子进程中的accept()过程进行加锁。

初始化最后需要开始创建预定个数的子进程,调用startup_children()函数创建指定个数的子进程,该函数会检查scoreboard的空闲插槽,在空闲插槽上调用make_child()函数来在该插槽位置处创建一个子进程,该函数设置scoreboard中进程的状态,并fork()一个子进程,将子进程的pid写入到scoreboard对应的插槽处,子进程创建之后设置SIGHUP和SIGTERM信号,这两个信号对应的回调函数均为clean_child_exit()函数,该函数销毁内存池然后退出子进程。

make_child()函数执行成功后进行child_main()函数,即子进程的主循环。该函数看起来比较复杂,其实做的事情也很简单,首先是创建相关的内存池,对进程锁进行初始化(对于pthread进程锁对应是一个空函数,即无需进行初始化),将socket描述符加入到pollset中,这里的pollset也是apache抽象出来的概念,它的实现可以是kqueue/port/epoll/poll/select,具体采用哪种方式也是配置可选的。这里是我不太明白的地方,经常看到评论说nginx效率高于apache,当问起nginx效率高于apache的主要原因时,得到的答案很多都是nginx采用kqueue和epoll实现了高并发,其实感觉这个理由并不充分,我们可以看到apache同样也实现了kqueue和epoll的多路复用,如果这因为这个的话那apache没有理由会比nginx效率低多少的,另外也看到有说apache的进程管理机制占用内存过高,而且时常需要进行进程切换从而占用了CPU时间,这个说法可以接受,现在非常想去读下nginx的源码,想看看它到底是采用了什么样的机制带来了它如此之多的好评,下一步就可始阅读下nginx的源码,对比着apache,探索一些高并发服务器设计的最优方法。

子进程进入主循环之后会调用accept()方法,这个方法是需要进行加锁的,之后创建一个新的连接对象,并调用HOOK函数对连接进行处理,HOOK机制是apache模块化很重要的一种机制,在主程序中调用HOOK函数,具体的实现由具体的模块来定义。

父进程在创建完子进程之后也进行主循环,监控活动子进程的数目,并通过一定的调度使用子进程数目维护一个平衡,父进程使用waitpid()函数来检测子进程的退出情况,如果有进程退出,则创建一个新的进程来替代已结束的进程从而维持总数的一个平衡。

当然apache还有平稳启动机制,关于平衡启动的代码我暂时略过了,没有细读,以后有时间再回过头来仔细研究。

原创文章,转载请注明: 转载自basic coder

本文链接地址: http://basiccoder.com/apache-mpm-prefork-design-intro.html

分类: C/C++ 标签:
  1. 2011年5月16日23:42 | #1

    赞 牛文!

  2. crazyhadoop
    2011年5月17日07:35 | #2

    不错,言简意赅,思路清晰,学习啦

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。