下面總結在最近招聘中常問的一個問題
PHPer當被問到你的程式效能如何?程式的並發可以達到多少?程式的瓶頸在哪兒?為了滿足業務需求應該購買多少台伺服器?負載平衡中php應用伺服器需要多少台?
可能這些問題在面試中會設定一個應用的情境及一些前提條件,讓面試的人去設計,並提出看法建議,能夠回答得很好的人還是比較少的。
今天我們來談談LNMP的並發考慮和資源分派。首先弄清楚幾個概念
LNMP中的N是nginx充當Web Server
內容的分發者,會在檔案系統找到相應的檔案,就返回給瀏覽器,如:nginx。如果是靜態檔案,就可以直接返回,但是如果是index.php需要解析並執行的指令檔時,Web Server就無力了,需要將請求轉寄給相應的指令碼語言的解析器來解釋並執行,最終將程式的執行結果,返回給Web Server,再返回給瀏覽器。
LNMP中的P是php充當後端的邏輯處理常式
那麼php與nginx的常規協作方式是如何的呢?需要我們明確幾個概念
cgi
通用閘道介面,是HTTP協議中描述的,Web Server與後端處理常式處理序間通訊的協議
php-cgi
php實現了cgi協議,使得web server與php共同完成一個動態網頁的請求響應
fastcgi
是為瞭解決cgi效能問題,而規範的另外一種協議,為什麼說解決cgi效能問題,因為在面對各大中型網站的業務需求中,cgi程式表現得越來越無力,因為cgi程式在每次接收到請求時都需要啟動新的進程,並初始化環境,然後執行程式,具體的協議內容,在此不引述。
php-fpm
實現了fastcgi協議,是php-cgi的進程管理器,解決高並髮網站的效能問題。
在最終回答LNMP的並發考慮與資源分派還需要明確的幾個概念
並發
一般由單位內完成的請求數來衡量,如,每秒事務數(TPS),每秒HTTP請求數(HPS),每秒查詢數(QPS)。通常情況下,我們說PHP的並發,都是指一秒內PHP完成的動態請求的次數。如某網站高峰期的動態請求並發為5000每秒,這個數字不算太高,但也不低。一般日活躍使用者數在1000萬-5000萬的網站應用程式才能達到這個層級。
效能
一般是指應用程式的處理速度,如果php的應用程式,開啟一個頁面(執行一個指令碼程式)通常需要在50-100ms完成,這對程式的效能要求還是比較高的。但是這還僅僅只是程式處理,php處理完成之後,還要交給web server,web server再將資料返回瀏覽器,這中間會有一個網路延遲,通常網路正常的情況下,需要大約100ms,最終一個動態網頁的請求大約200ms(理想的情況下)可以到達使用者瀏覽器端(僅僅是一個html結構)。
資源分派
1)php-fpm進程數
按照上面的描述,並發為5000每秒,每個請求完成大約200ms(具體頁面要具體分析,這裡只是一個理想值),如果只有5台PHP應用程式伺服器,那麼每台機器平均為並發1000每秒,如果是使用nginx+php-fpm的架構,php-fpm的php-cgi進程管理器的配置應該如何呢?我計算的結果為(具體的配置項說明在後文):
pm=static
pm.max_children=100
上面的100是如何得來的,由於機器平均並發為1000每秒,每個動態請求的處理時間為100ms,也就是說1個php-fpm的worker處理進程在1秒內可以處理10個請求,100個php-fpm的worker處理進程,就可以處理1000個請求。
當然需要結合伺服器硬體資源來進行配置,如果配置不當,很容易在請求高峰期或者流量猛增導致伺服器宕機。
2)網路頻寬
網路頻寬也會是一個重要的因素,如果你的服務處理很強,但是使用者的請求和響應不能及時到達也是白忙活,這個參數如何計算呢?
並發5000每秒,每個請求的輸出為20K,則5000x20K=100000K=100M
這就要求你的公網負載平衡器外網頻寬至少要達到100M
3)記憶體
上述中100個php-fpm的worker處理進程,理論上如果伺服器只運行php-fpm,那麼我們可以將伺服器記憶體的一半分配給php-fpm,通常情況下,我們可以認為一個php-fpm的worker處理進程佔用記憶體20M,那麼100x20M=2G,也就是說明伺服器的記憶體大約為4G
4)CPU
由於php-fpm是一個多進程的模型應用,CPU進程調度消耗也是很大的,並且PHP應用程式有問題也會導致CPU佔用率高,這就沒有量化的指標,需要具體情況具體分析了。但是有一個小建議,可以部署一個crontab每隔一分鐘檢測cpu佔用率超過多少就kill掉相應的php-fpm的worker處理進程。
5)nginx與php-fpm使用unix域通訊端代替tcp socke進行通訊
這個配置挺關鍵的,純echo的ab測試,採用unix域通訊端每秒請求數提升10%-20%
即nginx中配置:
fastcgi_pass unix:/data/server/var/php/php-fpm.sock;
php-fpm.conf中配置:
listen = /data/server/var/php/php-fpm.sock
最後遇到很多同學對php-fpm的進程管理器的核心配置不太瞭解,下面是我翻譯的配置說明:
首先php-fpm相關的配置項有:
1)pm
進程管理器以控制子進程的數量,可能的值有
static 一個固定的值,由pm.max_children指定
dynamic 動態(工作方式和Apache的prefork模式一致),但是保持至少一個,由
pm.max_children 在同一時間最大的進程數
pm.start_servers php-fpm啟動時開啟的等待請求到來的進程數
pm.min_spare_servers 在空閑狀態下,啟動並執行最小進程數,如果小於此值,會建立新的進程
pm.max_spare_servers 在空閑狀態下,啟動並執行最大進程數,如果大於此值,會kill部分進程
ondemand 啟動時不會建立進程,當請求達到時建立子進程處理請求
pm.max_children 在同一時間最大的進程數
pm.process_idle_timeout 空閑多少秒之後進程會被kill
pm = dynamic
2)pm.max_children
在同一時間最大的進程數
pm.max_children = 120
3)pm.start_servers
php-fpm啟動時開啟的等待請求到來的進程數,預設值為:min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 80
4)pm.min_spare_servers
在空閑狀態下,啟動並執行最小進程數,如果小於此值,會建立新的進程
pm.min_spare_servers = 60
5)pm.max_spare_servers
在空閑狀態下,啟動並執行最大進程數,如果大於此值,會kill部分進程
pm.max_spare_servers = 120
6)pm.process_idle_timeout
空閑多少秒之後進程會被kill,預設為10s
pm.process_idle_timeout = 10s
7)pm.max_requests
每個進程處理多少個請求之後自動終止,可以有效防止記憶體溢出,如果為0則不會自動終止,預設為0
pm.max_requests = 5000
8)pm.status_path
註冊的URI,以展示php-fpm狀態的統計資訊
pm.status_path = /status
其中統計頁面資訊有:
pool 進程池名稱
process manager 進程管理器名稱(static, dynamic or ondemand)
start time php-fpm啟動時間
start since php-fpm啟動的總秒數
accepted conn 當前進程池接收的請求數
listen queue 等待隊列的請求數
max listen queue 自啟動以來等待隊列中最大的請求數
listen queue len 等待串連socket隊列大小
idle processes 當前閒置進程數
active processes 活動的進程數
total processes 總共的進程數(idle+active)
max active processes 自啟動以來活動的進程數最大值
max children reached 達到最大進程數的次數
9)ping.path
ping url,可以用來測試php-fpm是否存活並可以響應
ping.path = /ping
10)ping.response
ping url的響應本文
ping.response = pong