並發的HTTP請求,apache是如何響應的,以及如何調用php文件的?

LAMP平台,apache應該是使用prefork模式,我想問一下,prefork創建的眾多進程是單獨監聽請求,還是說監聽請求,然後再對請求處理,然後再返回用戶?

如果有100個用戶同時請求index.php文件,是怎麼樣進行執行的?這100個用戶是需要排隊請求一個文件呢?還是對該文件創建若個副本?

如果是100個用戶排隊請求一個文件的話,那和1個用戶同時訪問100次是一樣的了吧?

另外,如果測試一個網站能同時承載的最大用戶數呢?

謝謝諸位


首先需要說的是,要想搞明白這些問題,你需要相當多的基礎知識.

從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
https://heguangyu5.github.io/httpd/test-keepalive.php

test-keepalive-timeout-max.php

https://heguangyu5.github.io/httpd/test-keepalive-timeout-max.php


比方說,我的電腦上(Ubuntu+i5-3230M),一個PHP請求耗時10ms(毫秒),那就可以估算,1個CPU每秒(每1000毫秒)就能處理100個請求,因為i5-3230M有四個邏輯核心,所以就能估算每秒最多能處理400個請求.每個請求的耗時在瀏覽器的開發者工具如Firefox的Firebug里就能看到.如果要模擬並發測試,可以用Apache內置的ab工具,如:並發100,完成4000個請求:
ab -c100 -n4000 http://127.0.0.1:8080/index.php

Apache中,PHP一般是作為Apache的一個模塊來運行,也就是說Apache會把PHP腳本交個PHP模塊執行後返回結果.Apache的prefork MPM是一個多進程的架構,跟PHP-FPM類似,你可以看看我的一篇關於PHP-FPM的博文:
PHP FastCGI進程管理器PHP-FPM的架構


prefork工作模式是linux下apache安裝時候的默認工作模式,是使用最普遍的工作模式。為了能夠簡單的明白他的工作原理,下面是一個假設:

有一台正在運行的apache伺服器,用戶A訪問該apache的時候apache建立一個新的進程1處理用戶A的請求。

這時又有一個用戶B訪問該apache,apache又建立一個新的進程2處理用戶B的請求。

後來又有用戶C,D,E訪問該apache,apache又建立三個進程3,4,5處理他們的請求。

如果每當一個新用戶訪問該apache,apache再建立一個新的進程處理用戶的請求,是不是太慢了呢?

所以apache的prefork模式在apache第一次啟動的時候就建立5個進程,等待用戶的連接請求,有一個用戶訪問,就有一個進程處理他的請求。

那麼如果有5個用戶同時訪問apache,apache第一次建立的5個進程全部用光了,所以apache就再從新在建立5個進程,等待下一批用戶的請求。

prefork模式會根據伺服器的硬體情況,設定apache最多只能同時建立256個進程。再多的請求就只能等待前面的進程處理完畢在進行處理。

假設完畢!

上面的假設就是prefork模式的工作原理。但是上面假設中具體的數字不是定死的,而是通過prefork模式的配置來設置的。下面是http.conf中的配置信息。

*帶井號的為注釋部分

prefork模式的幾個重要的參數:

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

名稱 默認值 說明

StartServers 5 apache啟動時候默認開始的進程數

MinSpareServers 5 最小的閑置進程數

MaxSpareServers 10 最大的閑置進程數

ServerLimit 256 最大的進程總數(參考,實際看MaxClients)

MaxClients 256 最大的進程總數

MaxRequestsPerChild 4000 每個進程處理的最多請求數

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

(1)StartServers:這個看了就明白了。

(2)MinSpareServers:舉個例子就明白了。

apache在沒有用戶訪問時候有5個閑置的進程,如果有一個用戶訪問網站。則閑置的進程就只有4個,這個值小於MinSpareServers,所以apache就以第一秒1個進程,第二秒2個進程,第三秒4個進程的速度新建空閑進程。直到大於等於MinSpareServers個空閑進程才結束。

(3)MaxSpareServers:還是舉個例子就明白了。

apache在沒有用戶訪問時候有5個閑置的進程,如果有5個用戶同時訪問網站。則閑置的進程就只有0個,這個值小於 MinSpareServers,所以apache就以第一秒1個進程,第二秒2個進程,第三秒4個進程的速度新建空閑進程。直到大於等於 MinSpareServers個空閑進程才結束。在這個例子中直到第三秒,一共生成1+2+4個進程才能滿足大於等於MinSpareServers的要求。後來這5個用戶訪問完apache,訪問結束,關閉瀏覽器。所以apache就有了5+7個空閑的進程。這時空閑的進程比較多,apache就開始關閉一些進程,直到滿足小於MaxSpareServers個空閑進程才結束。如果該值小於MinSpareServers則apache默認將該值設置成MinSpareServers+1。

(4)ServerLimit:這個參數是控制apache的進程總數的,那為什麼會有兩個參數控制apache的進程總數呢?這個參數在apache1的時代是沒有的,因為那個時候有個256M內存的伺服器就很厲害了。後來apache2的時代到來,伺服器的硬體也得到升級。很多伺服器都是4G內存,還有很多比4G內存大的伺服器出現。apache1的時代只有一個MaxClients參數控制進程總數就夠了,而這個參數最大值是256定死了。但是到了apache2的時代必須調整ServerLimit值大於256才能使MaxClient支持大於256的值。

(5)MaxClients:apache最大的進程數。apache1的時代只有一個MaxClients參數控制進程總數就夠了,而這個參數最大值是256定死了。但是到了apache2的時代必須調整ServerLimit值大於256才能使MaxClient支持大於256的值。

(6)MaxRequestsPerChild:舉個例子就明白了。

apache在沒有用戶訪問的時候有5個空閑進程。當一個用戶訪問網站,訪問完又離開。則apache的第一個進程就處理了一個請求,從新進入閑置狀態。再有一個用戶訪問網站,訪問完後離開。則apache的第一個進程就處理了1+1個請求。這樣繼續訪問3998個用戶,這個進程就處理了4000個請求,之後就自動關閉這個進程。這個時候apache就只有4個限制的進程,小於MinSpareServers值所以apache從今建立一個空閑進程。至於為什麼處理完4000個請求就要關閉這個進程呢?答案之一:為了防止內存的泄露。

下面圖文介紹這些參數(要深刻理解一個知識就要親自去體驗)

測試環境參數

說明

操作系統

centos4.5(虛擬機)

伺服器

apache2.2.6

內存

1G

伺服器地址

192.168.212.128

客戶端地址

192.168.212.1

測試文件index.php

&

for($i = 0;$i &<= 10;$i++){

echo date("H:i:s",time());

echo "&
";

sleep(10);

}

?&>

測試http.conf的prefork模式設置:(設置有點極端,但是可以很好的理解說明)

首先查看apache第一次啟動時候的空閑進程:

linux(centos)下查看apache的進程可以使用#ps -ef|grep httpd命令,查看apache進程的內存使用情況可以使用#ps –U apache –u apache u命令

#service httpd restrat:重啟apache,初始化進程

#ps -ef|grep httpd:用戶名為apache的才是apache用於處理用戶請求的進程

#ps –U apache –u apache u:查看用戶名為apache的進程(即apache為了處理用戶請求而建立的進程詳情)

當前為沒有任何用戶訪問時候apache建立StartServers=1個進程

(1)當有一個用戶訪問(通過本機上的ie瀏覽器訪問虛擬主機上apache伺服器模式該情景)

可以看到apache的進程增加為2個,為什麼呢?當唯一的空閑進程時候完之後,空閑進程小於MinSpareServers,apache就從新建立一個進程。注意查看RSS值(該進程的內存使用量KB)為4424的空閑進程。

再看看訪問完成之後的apache進程數。

可以看到還是只有兩個進程,而且這兩個進程都是空閑的。

(2)當有兩個用戶訪問(通過本機上的兩個ie瀏覽訪問虛擬主機上的apache伺服器模擬該情景)

可以看到有兩個用戶訪問的時候apache的進程數增加為3個。為什麼呢?當本來空閑2個進程時候完之後,空閑進程小於MinSpareServers,apache就從新建立一個進程。注意查看RSS值(該進程的內存使用量KB)為4424的空閑進程。

再看看訪問完成之後的apache進程數。

可以看到空閑的進程數又減少為2個。為什麼呢?當兩個用戶訪問完成之後兩個用戶的進程都會關閉,這時候就會有3個空閑進程,這個值大於MaxSpareServers=2的值。所以apache會自動關掉一個進程滿足MaxSpareServers=2的值。

(3)當有三個用戶同時訪問(通過本機上的三個ie瀏覽訪問虛擬主機上的apache伺服器模擬該情景)

可以看到apache的進程有3個,而這三個進程都在處理用戶的請求,沒有多餘的空閑進程產生。為什麼呢?因為在上面我們設置了MaxClients的值為3。所以apache最多只會生成3個進程。

再看看這三個用戶訪問完成之後的apache進程數。

可以看到空閑的進程數又減少為2個。為什麼呢?當三個用戶訪問完成之後三個用戶的進程都會關閉,這時候就會有3個空閑進程,這個值大於MaxSpareServers=2的值。所以apache會自動關掉一個進程滿足MaxSpareServers=2的值。

(4)當有四個用戶同時訪問(通過本機上的四個ie瀏覽訪問虛擬主機上的apache伺服器模擬該情景)

可以看到雖然有四個用戶同時訪問,但是apache創建的進程數還是只有3個。就是說apache只對前面的3個用戶請求進行了處理。第4個用戶只能等待。

這個時候查看apache的TCP連接情況。(對於TCP連接不太理解的可以點擊查看)

可以看到這4個用戶都已經建立了連接。但是apache只處理了前面的3個請求。處理完3個請求在處理最後一個請求,這點可能從文件的列印內容看到。四個用戶幾乎是同時訪問的。

可以看到最後一個用戶的請求時間是前面三個用戶訪問完成之後開始的。

依次類推,當有7個用戶同時訪問時候又是什麼樣的?只貼幾張圖,看了就明白了。

結論

如果從頭到尾詳細讀過本篇文章,會得出如下的結論:

1,apache是嚴格按照prefork模式的配置參數來進程分配和管理的。不像網上有些文章那樣修改了某個值,apache不起作用。只能說您改的不對,或者說您對上面的內容還不夠理解。

2,MaxClients值就是apache的最大進程數。不像網上有些文章說的這個值越大越好(有的文章既然推薦該值為4000),您可以從上面的進程圖中看到apache的每個進程的%MEM(內存佔用百分比)值為0.5%左右。所以這個值的具體設置的最大數位:100/0.5 = 200。而這也只是在這個伺服器只有一個apache的情況下。如果伺服器有其他程序需要佔用內存(比如mysql)這個值要小於200。你總不能把操作系統的所有內存都給apache吧?

3,每個瀏覽器是一個用戶,每個用戶就是一條進程。明白意思了吧?我的這台伺服器的並發量只有200。就是說我的這台伺服器只能支持同時200個用戶訪問,再多的用戶訪問就只能是等待。或者說我這台伺服器只支持200個瀏覽器的訪問。關於伺服器的並發我會在下面一片文章中詳細講解。

4,200的並發量很小嗎?要知道apache處理數據的速度是相當快的。一條正常的首頁訪問可能就一秒鐘處理完畢。所以在假想的狀態下,我的這台伺服器,每分鐘可以訪問60*200=12000個用戶。每天可以訪問12000*60*24=17280000個用戶的訪問。這當然是在完全飽和的訪問狀態的假想數據。下一篇文章我會詳細講解網站並發,請求,連擊等內容。

5,如果訪問的用戶數大於MaxClients的數,多出的用戶不會立刻斷掉連接,還是會建立TCP連接。只不過會等待前面的用戶處理完在得到相應。在php.ini,http.conf,操作系統設定的超時時間內得不到相應才會斷掉連接。


apache 監聽某個介面,比如8080,當http請求來了,apache接收到,把請求的位元組流轉成httprequest, 然後根據映射規則找到具體的php文件,處理完,httpresponse再把結果寫回去,大致是這個流程。


推薦閱讀:

用 HHvm 運行 WordPress 是用 Apache 好還是 Nginx 好一點?
Web 測試 有沒有比Apache Jemeter更好的工具,windows或者macos上的?

TAG:PHP | Apache | HTTP | 並發測試 |