PHP Web System Optimization(undone)

目錄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

相關文章
相關標籤/搜索