PHP 的多程序併發
介紹
php工程師在開發過程中,往往以只需要序列編寫業務邏輯即可,完全不用考慮併發。通常部署時配置好nginx, fpm就行。就這樣介面便併發的提供web服務了。那到底是怎麼實現的呢?一個來自客戶端的請求,首先會到達nginx這樣的web伺服器,然後通過fastcgi協議將請求傳遞到php這樣的處理程式,php執行後返回結果給nginx,nginx最終返回客戶端。nginx是高併發伺服器,這裡我們不細說。主要看看php是怎麼實現併發的。
fpm(Fastcgi Process Manager),支援fastcgi外,主要功能是程序管理。以多程序方式工作,由一個master程序與多個worker程序組成,master程序主要功能是管理worker程序,例如維持worker數量在一定範圍內。而worker程序則是真正處理請求的,對於單個worker程序,是序列執行接收到的每個請求。這種簡單的實現就導致有多少個worker最多同時處理的請求數量就有多少。這也是不能高併發的主要原因。
實現
多程序實現是靠fork這個系統呼叫完成。
-
master初始化後,按配置要求fork出worker程序,自身進入事件迴圈處理中。 worker退出執行請求處理邏輯
2. worker正常執行max_requests個請求正常退出
細節
1. master如何獲取worker的執行資訊?
這裡是通過共享記憶體來通訊的。在master初始化過中,會進行scoreboard結構的分配(每個worker pool分配一個)。每個worker在處理請求的過程中,會先後將
fpm_request_accepting,
fpm_request_reading_headers,
fpm_request_info,
fpm_request_executing,
fpm_request_end,
fpm_request_finished置位到request_stage, master就可以直接在獲取worker狀態,例如在多個worker都在等待請求時,減少worker數到最小空閒數。
2. master進入事件迴圈有哪些處理?
訊號處理 : master對接收到的資訊進行處理,比如收到關閉資訊,則傳送訊號給worker程序(master與worker程序通訊的主要方式是訊號)
程序檢查定時器 : 每隔一段時間,根據不同策略將worker數量維持在要求的範圍內。
超時檢查定時器 : 若一個請求處理時長超過request_terminate_timeout, 那master便會發送term訊號殺掉worker.
3. 若多個worker同時獲取一個請求?
若多個worker都阻塞在fcgi_accept_request, 突然有一個請求到達,為了防止多個程序對accept進行搶佔,php在這裡加入了鎖機制。FCGI_LOCK, FCGI_UNLOCK, 在linux中沒實現。因為在linux2.6核心中, accept系統呼叫已不存在驚群了。
存在的問題
1. fpm是通過多程序實現併發的。併發主要受限於程序的數量,程序的數量又受限於系統的記憶體,cpu。一般伺服器開500+ worker就可以了。但是這些worker又不能充分利用系統資源,主要是大多處理要集中在操作mysql, redis這樣的遠端資源上了,發起請求後,worker本身就阻塞等待中。真是受限於資源,不能太多程序,而在手的程序又不能完全傳送作用。
2. worker序列化處理max_requests個請求,不能高併發。fastcgi_finish_request這些也只是優先返回客戶端響應,還是阻塞處理後續邏輯。
3. 基於上面1,2兩點,我們知道了php處理能力有限。在特殊情況下,會導致後果呢? 1. 依賴的redis,mysql被慢查詢拖住時,多個worker會同時請求超時,到時後續請求得不到處理,直接導致一段時間內請求雪崩。這時應該重啟fpm還是解決後端資料資源? 2. 若你的php介面中還代理了其他服務,請合理設定超時,原因同上。
4. max_requests的初衷是為了避免第三方擴充套件引起的記憶體洩漏問題。但是,在請求均勻的情況下,多個 worker同時結束,master為了維持worker平衡,會瞬間大量fork,從監控來看會發生鋸齒狀。百度技術人員通過對max_requests進行隨機,優化了這種情況,可以參考:
http://www.sohu.com/a/163046759_487482
總結
通過fpm這個程序管理器,php實現了讓開發者不考慮併發,專注於業務開發,保證了較快的開發效率,但是又是這種簡單的多程序實現的併發,不能承載高併發的業務。在技術選型上,開發者要牢記這一點!