目錄php
0. 引言 1. PHP Pool 2. listen 3. Process Manage(PM) 4. pm.max_children 5. PHP DB Connection Pool(數據庫鏈接池)
0. 引言html
0x1: WEB系統的性能優化須要考慮哪些方面前端
對於一個WEB系統來講,從client發起請求,到服務器端處理,最後到返回到client顯示結果,在大多數狀況下這是一個較長的鏈路,其中的每個環節都存在能夠優化性能的熱點,例如mysql
1. 將多臺web server配置成集羣模式,在集羣的前端(邏輯)部署Load Balance Server,將客戶端的訪問請求"平均地"負載到集羣的子節點server中 2. 在Load Balance Server和集羣之間部署Cache Server,例如對於典型的DB驅動型的應用,使用Redis代替直接Mysql鏈接就是一個很好的實踐 1) 對於insert操做,在向mysql插入的同時,向redis中也保存一份 2) 對於select操做,優先從redis中查詢,若是命中失敗,再從mysql中查詢 3. 集羣中的web server採用nginx+php-fpm的組合,nginx在高併發狀況下的表現比apache要更好 4. 針對web server上的業務代碼進行邏輯優化 5. 將單點的DB,例如mysql,改成讀寫分離的主從集羣架構 6. 使用DB Connection Pool(數據庫鏈接池)代替每次請求都從新建立並銷燬DB鏈接的操做
0x2: PHP解析工做方式linux
首先要明白的是,對於PHP來講,SAPI是PHP必由的和外部交互的通道,所謂的PHP和外部不一樣的通訊交互方式的實現差異,都在SAPI這個層面實現,函數sapi_cgibin_ub_write告訴zend如何輸出數據nginx
SAPI提供了一個和外部通訊的接口,使得PHP能夠和其餘應用進行交互數據,PHP默認提供了不少種SAPIgit
1. Apache:mod_php5 2. IIS:ISAPI 3. SHELL:CLI
PHP從本質上講是一個腳本代碼解釋執行容器,而這個解析請求是由WEB容器主動觸發的,通常來講,WEB server和PHP的配合方式有如下幾種github
1. Apache + mod_php 2. Apache + mod_fastcgi 3. nginx + php-fpm
1. Apache + mod_php web
這是在LAMP架構中最經常使用的組合方式,它把PHP編譯爲APACHE的一個"內置模塊",讓apache http server自己可以支持php語言,不須要每個請求都要啓動一次"php解釋器"來解釋執行php
當有一個php請求過來時,直接在httpd進程裏完成php的解釋執行,並將結果返回redis
在mod_php的源碼中,函數sapi_cgibin_ub_write()直接調用了apache的ap_write函數,因此,用mod_php,咱們能夠把php和apache當作一個模塊,二者綁定在一塊兒
2. Apache + mod_fastcgi
fastcgi是http server和第三方程序的交互方式,它能夠接收web server發起的請求,解釋輸入信息,並將處理後的結果返回給服務器。mod_fastcgi是在apache支持下fastcgi協議的模塊
apache啓動後,mod_fastcgi會在啓動多個cgi程序,即php-cgi腳本,具體的數目經過配置來指定,當有http請求到來後,httpd進程會選擇其中一個當前空閒的php-cgi程序來執行,執行的方式和mod_php相似,也是經過php-cgi提供的sapi完成交互
從源碼中能夠看到,對於cgi的sapi它是把結果輸出到fastcgi提供的stdout上,fastcgi再將數據返回給httpd完成交互
3. nginx + php-fpm
php-fpm的出現是爲了解決在fastcgi模式下cgi管理的問題,php-fpm是一個相似於spwn-cgi的管理工具,能夠和任何支持遠端fastcgi的web server工做
FPM(FastCGI 進程管理器)用於替換 PHP FastCGI 的大部分附加功能,對於高負載網站是很是有用的
1. 支持平滑中止/啓動的高級進程管理功能 2. 能夠工做於不一樣的 uid/gid/chroot 環境下,並監聽不一樣的端口和使用不一樣的 php.ini 配置文件(可取代 safe_mode 的設置) 3. stdout 和 stderr 日誌記錄 4. 在發生意外狀況的時候可以從新啓動並緩存被破壞的 opcode 5. 文件上傳優化支持 6. "慢日誌" - 記錄腳本(不只記錄文件名,還記錄 PHP backtrace 信息,可使用 ptrace或者相似工具讀取和分析遠程進程的運行數據)運行所致使的異常緩慢 7. fastcgi_finish_request() - 特殊功能:用於在請求完成和刷新數據後,繼續在後臺執行耗時的工做(錄入視頻轉換、統計處理等) 8. 動態/靜態子進程產生 9. 基本 SAPI 運行狀態信息(相似Apache的 mod_status) 10. 基於 php.ini 的配置文件
Relevant Link:
http://huoding.com/2014/12/25/398 http://php-fpm.org/ http://php.net/manual/zh/install.fpm.php http://wenku.baidu.com/view/887de969561252d380eb6e92.html http://baike.baidu.com/view/4168033.htm
1. PHP Pool
這裏的池指的是"PHP進程池",PHP容許同時啓動多個池,每一個池使用不一樣的配置,各個池之間尊重彼此的主權領土完整,互不干涉內政
"Pool對象(線程池)"是多個 Worker 對象的容器,同時也是它們的控制器。它是對 Worker 功能的高層抽象,包括按照 pthreads 須要的方式來管理應用的功能
<?php class Config extends Threaded { // shared global object protected $val = 0, $val2 = 0; protected function inc(){++$this->val;} // protected synchronizes by-object public function inc2(){++$this->val2;} // no synchronization } class WorkerClass extends Worker { protected static $worker_id_next = -1; protected $worker_id; protected $config; public function __construct($config) { $this->worker_id = ++static::$worker_id_next; // static members are not avalable in thread but are in 'main thread' $this->config = $config; } public function run() { global $config; $config = $this->config; // NOTE: setting by reference WON'T work global $worker_id; $worker_id = $this->worker_id; echo "working context {$worker_id} is created!\n"; //$this->say_config(); // globally synchronized function. } protected function say_config() { // 'protected' is synchronized by-object so WON'T work between multiple instances global $config; // you can use the shared $config object as synchronization source. $config->synchronized(function() use (&$config) { // NOTE: you can use Closures here, but if you attach a Closure to a Threaded object it will be destroyed as can't be serialized var_dump($config); }); } } class Task extends Stackable { // Stackable still exists, it's just somehow dissappeared from docs (probably by mistake). See older version's docs for more details. protected $set; public function __construct($set) { $this->set = $set; } public function run() { global $worker_id; echo "task is running in {$worker_id}!\n"; usleep(mt_rand(1,100)*100); $config = $this->getConfig(); $val = $config->arr->shift(); $config->arr[] = $this->set; for ($i = 0 ; $i < 1000; ++$i) { $config->inc(); $config->inc2(); } } public function getConfig(){ global $config; // WorkerClass set this on thread's scope, can be reused by Tasks for additional asynch data source. (ie: connection pool or taskqueue to demultiplexer) return $config; } } $config = new Config; $config->arr = new Threaded(); $config->arr->merge(array(1,2,3,4,5,6)); class PoolClass extends Pool { public function worker_list() { if ($this->workers !== null) return array_keys($this->workers); return null; } } $pool = new PoolClass(3, 'WorkerClass', [$config] ); $pool->worker_list(); //$pool->submitTo(0,new Task(-10)); // submitTo DOES NOT try to create worker $spammed_id = -1; for ($i = 1; $i <= 100; ++$i) { // add some jobs if ($spammed_id == -1 && ($x = $pool->worker_list())!= null && @$x[2]) { $spammed_id = $x[2]; echo "spamming worker {$spammed_id} with lots of tasks from now on\n"; } if ($spammed_id != -1 && ($i % 5) == 0) // every 5th job is routed to one worker, so it has 20% of the total jobs (with 3 workers it should do ~33%, not it has (33+20)%, so only delegate to worker if you plan to do balancing as well... ) $pool->submitTo($spammed_id,new Task(10*$i)); else $pool->submit(new Task(10*$i)); } $pool->shutdown(); var_dump($config); // "val" is exactly 100000, "val2" is probably a bit less // also: if you disable the spammer, you'll that the order of the "arr" is random. ?>
默認狀況下,PHP 只啓用了一個池,全部請求均在這個池中執行。一旦某些請求出現擁堵之類的狀況,那麼極可能會連累整個池出現火燒赤壁的結局;若是啓用多個池,那麼能夠把請求分門別類放到不一樣的池中執行,此時若是某些請求出現擁堵之類的狀況,那麼只會影響本身所在的池,從而控制故障的波及範圍
Relevant Link:
http://php.net/manual/zh/class.pool.php http://netkiller.github.io/journal/thread.php.html
2. listen
從本質上來說,PHP是一個腳本語言的解釋服務器,它接收來自web容器的解析請求,並返回動態解析的結果
雖然 Nginx 和 PHP 能夠部署在不一樣的服務器上,可是實際應用中,多數人都習慣把它們部署在同一臺服務器上,如此就有兩個選擇
1. TCP:經過TCP鏈接的方式向PHP解釋器發送解析請求 2. Unix Socket:經過Unix Socket方式向PHP解釋器發送解析請求
和 TCP 比較,Unix Socket 省略了一些諸如 TCP 三次握手之類的環節,因此相對更高效,不過須要注意的是,在使用 Unix Socket 時,由於沒有 TCP 對應的可靠性保證機制,因此最好把 backlog 和 somaxconn 設置大些,不然面對高併發時會不穩定
Relevant Link:
https://blog.linuxeye.com/364.html http://www.cnxct.com/default-configuration-and-performance-of-nginx-phpfpm-and-tcp-socket-or-unix-domain-socket/
3. Process Manage(PM)
進程管理有動態和靜態之分
1. 動態模式 通常先啓動少許進程,再按照請求數的多少實時調整進程數。如此的優勢很明顯:節省資源;固然它的缺點也很明顯:一旦出現高併發請求,系統將不得不忙着 FORK 新進程,必然會影響性能 2. 靜態模式 一次性 FORK 足量的進程,以後無論請求量如何均保持不變。和動態模式相比,靜態模式雖然消耗了更多的資源,可是面對高併發請求,它不須要執行高昂的 FORK
對於大流量高負載WEB應用來講,使用靜態模式進行進程預分配是一個很好的最佳實踐
4. pm.max_children
一個 CPU 在某一個時刻只能處理一個請求。當請求數大於 CPU 個數時,CPU 會劃分時間片,輪流執行各個請求,既然涉及多個任務的調度,那麼上下文切換必然會消耗一部分性能,從這個意義上講,進程數應該等於 CPU 個數,如此一來每一個進程都對應一個專屬的 CPU,能夠把上下文切換損失的效率降到最低。不過這個結論僅在請求是 CPU 密集型時纔是正確的,而對於通常的 Web 請求而言,多半是 IO 密集型的,此時這個結論就值得商榷了,由於數據庫查詢等 IO 的存在,必然會致使 CPU 有至關一部分時間處於 WAIT 狀態,也就是被浪費的狀態。此時若是進程數多於 CPU 個數的話,那麼當發生 IO 時,CPU 就有機會切換到別的請求繼續執行,雖然這會帶來必定上下文切換的開銷,可是總比卡在 WAIT 狀態好多了。
Relevant Link:
http://www.guangla.com/post/2014-03-14/40061238121 http://forum.nginx.org/read.php?3,222702
5. PHP DB Connection Pool(數據庫鏈接池)
Relevant Link:
http://gonzalo123.com/2010/11/01/database-connection-pooling-with-php-and-gearman/
Copyright (c) 2014 LittleHann All rights reserved