并发的HTTP请求,apache是如何响应的,以及如何调用php文件的?

LAMP平台,apache应该是使用prefork模式,我想问一下,prefork创建的众多进程是单独监听请求,还是说监听请求,然后再对请求处理,然后…
关注者
105
被浏览
13,089

5 个回答

首先需要说的是,要想搞明白这些问题,你需要相当多的基础知识.

从C,socket到linux kernel, apache源码都需要.

----------------------

一个 toy http server 大概是这个样子的:

socket()
bind()
listen()
while (conn = accept()) {
    fork();
    // child process request
    // parent wait child exit
}

apache prefork 是这个样子的:

socket()
bind()
listen()
// create proc mutex, scoreboard
// fork children, parent wait child exit, create new child
// child-1                                                  |  child-2
while (connection_count < MaxRequestsPerChild) {            |   while (connection_count < MaxRequestsPerChild) {
    mutex_lock()                                            |       mutex_lock()
    conn = accept()                                         |       conn = accept()
    mutex_unlock();                                         |       mutex_unlock();
    create_connection();                                    |       create_connection();
    set conn vhost lookup data(default_list->names)         |       set conn vhost lookup data(default_list->names)
    if lookup failed, base_server = "a.example.com"         |       if lookup failed, base_server = "a.example.com"
    do {                                                    |       do {
        read_request();                                     |           read_request();
        process_request();                                  |           process_request();
    } while (keepalive);                                    |       } while (keepalive);
    connection_count++;                                     |       connection_count++;
}                                                           |   }
child_exit();                                               |   child_exit();

简单解释一下:

apache有一个全局的mutex lock, 哪个子进程占有了这个lock,下个tcp连接就由它来accept.

accept成功后,就开始处理http请求了,如果客户端要求keep alive,那么处理完一个http请求后,等着客户端的下一个http请求.当然对同一个客户端处理的keep alive请求数和时间都是有限制的,要不然一个客户端长时间占有一个子进程,apache很快就不能响应其它客户端的请求了.

这个tcp连接断开后,子进程接着去处理下个tcp连接,当处理的tcp连接数达到MaxRequestsPerChild限制后,这个子进程就退出了.

在apache源码里,子进程叫child server.

一个网站能同时承载的最大用户数和并发用户数不是一个概念.

不过显然最大用户数要远大于并发用户数.

这里说下并发用户数.

sudo ./httpd -k start

mpm_prefork_module:
    StartServers 5 # ps aux | grep httpd 可验证, 1个root的process(parent)和5个daemon的process(child)
    MinSpareServers 5
    MaxSpareServers 10
    ServerLimit 256 (最大能调整到20W)
    MaxClients 256 (默认等于ServerLimit)
    MaxRequestsPerChild 10000

httpd默认启动5个child server,每一个tcp connection会占用一个child server,当前最多启动256个child server.
每个server处理过10000个tcp connection后,会exit. 注意是10000个tcp connection,不是http请求.
访问一个页面,浏览器可能会open多个tcp connection,而不是1个.

所以,为了支持N个并发连接,必须有N个child server.
为了支持N个并发用户,必须有 N * 浏览器open的tcp connection数(@see http://www.browserscope.org/?category=network, 大多数是6).
由于大多数浏览器都会keep alive,所以一旦child server accept了conn,这个conn就会占用这个server几秒时间.

其它需要考虑的就是kernel调度这么多child server执行消耗cpu,内存,io的问题了.

httpd支持的并发用户数 = MaxClients / 6 = 256 / 6 = 43
即使httpd处理http请求非常快,由于keepalive的原因,在几秒内child server并不能处理其它请求

验证: 写个php脚本,fork出300个child,每个child发一个http请求并keep alive. 前256个应该很快就返回了,后边的要等上几秒才能拿到结果.

httpd.conf 调整 MaxSpareServers 256
执行几次 php test-keepalive.php 让child server都启动起来
然后再执行一次 php test-keepalive.php, 观察结果: 有256个php client child 5秒结束,其它的10秒结束.


httpd默认KeepAliveTimeout=5,MaxKeepAliveRequests=100.
具体意思是: tcp connection建立后,可以每隔5秒发一个请求,最多发100个.
run test-keepalive-timeout-max.php
test-keepalive.php heguangyu5.github.io/ht test-keepalive-timeout-max.php
heguangyu5.github.io/ht

比方说,我的电脑上(Ubuntu+i5-3230M),一个PHP请求耗时10ms(毫秒),那就可以估算,1个CPU每秒(每1000毫秒)就能处理100个请求,因为i5-3230M有四个逻辑核心,所以就能估算每秒最多能处理400个请求.每个请求的耗时在浏览器的开发者工具如Firefox的Firebug里就能看到.如果要模拟并发测试,可以用Apache内置的ab工具,如:并发100,完成4000个请求: ab -c100 -n4000 127.0.0.1:8080/index.ph Apache中,PHP一般是作为Apache的一个模块来运行,也就是说Apache会把PHP脚本交个PHP模块执行后返回结果.Apache的prefork MPM是一个多进程的架构,跟PHP-FPM类似,你可以看看我的一篇关于PHP-FPM的博文: PHP FastCGI进程管理器PHP-FPM的架构