一個master進程,支持多個pool,每一個pool由master進程監聽不一樣的端口,pool中有多個worker進程.
每一個worker進程都內置PHP解釋器,而且進程常駐後臺,支持prefork動態增長.
每一個worker進程支持在運行時編譯腳本並在內存中緩存生成的opcode來提高性能.
每一個worker進程支持配置響應指定請求數後自動重啓,master進程會重啓掛掉的worker進程.
每一個worker進程能保持一個到MySQL/Memcached/Redis的持久鏈接,實現"鏈接池",避免重複創建鏈接,對程序透明.
使用數據庫持久鏈接時應該設置固定數量的worker進程數,不要使用動態的prefork模式.
經 @syaokun219 和 @IM鑫爺 糾正,如下兩句有誤:
master進程採用epoll模型異步接收和分發請求,listen監聽端口,epoll_wait等待鏈接.
而後分發給對應pool裏的worker進程,worker進程accpet請求後poll處理鏈接.
應該是:
master進程並不接收和分發請求,而是worker進程直接accpet請求後poll處理. php
經查看源代碼結構,php已經支持epoll模型,監聽時epoll,accept後poll
master進程不斷調用epoll_wait和getsockopt是用來異步處理信號事件和定時器事件.
這裏提一下,Nginx也相似,master進程並不處理請求,而是worker進程直接處理,
不過區別在於Nginx的worker進程是epoll異步處理請求,而PHP-FPM仍然是poll.
若是worker進程不夠用,master進程會prefork更多進程,
若是prefork達到了pm.max_children上限,worker進程又全都繁忙,
這時master進程會把請求掛起到鏈接隊列backlog裏(默認值是511).
1個PHP-FPM工做進程在同一時刻裏只能處理1個請求.
MySQL的最大鏈接數max_connections默認是151.
只要PHP-FPM工做進程數不超過151,就不會出現鏈接不上MySQL的狀況.
並且正常狀況下,也不須要開啓那麼多的PHP-FPM工做進程,
好比4個PHP-FPM進程就能跑滿4個核心的CPU,
那麼你開40個PHP-FPM進程也沒有任何意義,
只會佔用更多的內存,形成更多的CPU上下文切換,性能反而更差.
爲了減小每一個請求都重複創建和釋放鏈接的開銷,能夠開啓持久鏈接,
一個PHP-FPM進程保持一個到MySQL的長鏈接,實現透明的"鏈接池".
Nginx跟PHP-FPM分開,實際上是很好的解耦,PHP-FPM專門負責處理PHP請求,一個頁面對應一個PHP請求,
頁面中全部靜態資源的請求都由Nginx來處理,這樣就實現了動靜分離,而Nginx最擅長的就是處理高併發.
PHP-FPM是一個多進程的FastCGI服務,相似Apache的prefork的進程模型,
對於只處理PHP請求來講,這種模型是很高效很穩定的.
不像Apache(libphp.so),一個頁面,要處理多個請求,包括圖片,樣式表,JS腳本,PHP腳本等.
php-fpm從5.3開始才進入PHP源代碼主幹,以前版本沒有php-fpm.
那時的spawn-fcgi是一個須要調用php-cgi的FastCGI進程管理器,
另外像Apache的mod_fcgid和IIS的PHP Manager也須要調用php-cgi進程,
但php-fpm則根本不依賴php-cgi,徹底獨立運行,也不依賴php(cli)命令行解釋器.
由於php-fpm是一個內置了php解釋器的FastCGI服務,啓動時可以自行讀取php.ini配置和php-fpm.conf配置.
我的認爲,PHP-FPM工做進程數,設置爲2倍CPU核心數就足夠了.
畢竟,Nginx和MySQL以及系統一樣要消耗CPU.
根據服務器內存來設置PHP-FPM進程數很是不合理,
把內存分配給MySQL,Memcached,Redis,Linux磁盤緩存(buffers/cache)這些服務顯然更合適.
過多的PHP-FPM進程反而會增長CPU上下文切換的開銷.
PHP代碼中應該儘可能避免curl或者file_get_contents這些可能會產生較長網絡I/O耗時的代碼.
注意設置CURLOPT_CONNECTTIMEOUT_MS超時時間,避免進程被長時間阻塞.
若是要異步執行耗時較長的任務,能夠 pclose(popen('/path/to/task.php &', 'r')); 打開一個進程來處理,
或者藉助消息隊列,總之就是要儘可能避免阻塞到PHP-FPM工做進程.
在php-fpm.conf中把request_slowlog_timeout設爲1秒,在slowlog中查看是否有耗時超過1秒的代碼.
優化代碼,可以爲全部PHP-FPM工做進程減負,這個纔是提升性能的根本方法.
能讓CPU滿負荷運行的操做能夠視爲CPU密集型操做.
上傳和下載則是典型的I/O密集型操做,由於耗時主要發生在網絡I/O和磁盤I/O.
須要PHP認證的下載操做能夠委託爲Nginx的AIO線程池:
header("X-Accel-Redirect: $file_path");
至於上傳操做,好比能夠創建一個監聽9001端口的名爲upload的PHP-FPM進程池(pool),
專門負責處理上傳操做(經過Nginx分發),避免上傳操做阻塞到監聽9000端口的計算密集的www進程池.
這時upload進程池多開點進程也無所謂.
nginx.conf:
location = /upload.php {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9001;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
php-fpm.conf:
[www]
listen = 127.0.0.1:9000
pm = static
pm.max_children = 4
[upload]
listen = 127.0.0.1:9001
pm = dynamic
pm.max_children = 8
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 4
其中IO密集這個進程池[io]採用動態的prefork進程,好比這裏是繁忙時8個,空閒時4個.
利用PHP-FPM提供的池的隔離性,分離計算密集和I/O密集操做,能夠減小阻塞對整個PHP應用的影響.
補充:
info.php
<?php
if( isset($_POST['submit']) ) {
header('Content-Type: text/plain; charset=utf-8');
//chmod 777 uploads
move_uploaded_file($_FILES['upload_file']['tmp_name'], 'uploads/'.$_FILES['upload_file']['name']);
print_r($_FILES['upload_file']);
exit();
} else {
header('Content-Type: text/html; charset=utf-8');
}
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>PHP文件上傳測試</title>
</head>
<body>
<!-- enctype="multipart/form-data" 以二進制格式POST傳輸數據 -->
<form action="<?php echo pathinfo(__FILE__)['basename']; ?>" method="POST" enctype="multipart/form-data">
<div>文件1 <input type="file" name="upload_file" /></div>
<div><input type="submit" name="submit" value="提交" /></div>
</form>
</body>
</html>
Nginx和PHP-FPM的工做進程各自只開1個.
以2KB每秒上傳圖片:
time trickle -s -u 2 curl \
-F "action=info.php" \
-F "upload_file=@linux.jpeg;type=image/jpeg" \
-F "submit=提交" \
http://www.example.com/app/info.php
sudo netstat -antp|egrep "curl|nginx|fpm"
發現只有nginx和curl處於ESTABLISHED狀態,nginx和fpm都沒有被阻塞.
top -p 4075 可見Nginx單線程.
sudo strace -p 4075 可見Nginx調用recvfrom接收數據而且pwrite保存數據.
sudo strace -p 13751 可見PHP-FPM是在Nginx接收完成用戶上傳的數據時才獲取數據.
既然如此,我設想的另開PHP-FPM進程池處理上傳操做的用處就不是太大了.
在文件上傳過程當中PHP-FPM並不會被阻塞,由於Nginx接收完上傳的內容後才一次性交給PHP-FPM.
附:以2KB每秒下載圖片
time trickle -s -d 2 \
wget http://www.example.com/app/uploads/linux.jpeg -O /dev/null html