首页 > 代码库 > PHP Web System Optimization(undone)

PHP Web System Optimization(undone)

目录

0. 引言1. PHP Pool2. listen3. Process Manage(PM)4. pm.max_children5. PHP DB Connection Pool(数据库连接池)

 

0. 引言

0x1: WEB系统的性能优化需要考虑哪些方面

对于一个WEB系统来说,从client发起请求,到服务器端处理,最后到返回到client显示结果,在大多数情况下这是一个较长的链路,其中的每一个环节都存在可以优化性能的热点,例如

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解析工作方式

技术分享

首先要明白的是,对于PHP来说,SAPI是PHP必由的和外部交互的通道,所谓的PHP和外部不同的通信交互方式的实现差别,都在SAPI这个层面实现,函数sapi_cgibin_ub_write告诉zend如何输出数据

SAPI提供了一个和外部通信的接口,使得PHP可以和其他应用进行交互数据,PHP默认提供了很多种SAPI

1. Apache:mod_php52. IIS:ISAPI3. SHELL:CLI

PHP从本质上讲是一个脚本代码解释执行容器,而这个解析请求是由WEB容器主动触发的,一般来说,WEB server和PHP的配合方式有以下几种

1. Apache + mod_php 2. Apache + mod_fastcgi3. nginx + php-fpm  

1. Apache + mod_php

这是在LAMP架构中最常用的组合方式,它把PHP编译为APACHE的一个"内置模块",让apache http server本身能够支持php语言,不需要每一个请求都要启动一次"php解释器"来解释执行php
当有一个php请求过来时,直接在httpd进程里完成php的解释执行,并将结果返回

技术分享

在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/398http://php-fpm.org/http://php.net/manual/zh/install.fpm.phphttp://wenku.baidu.com/view/887de969561252d380eb6e92.htmlhttp://baike.baidu.com/view/4168033.htm

 

1. PHP Pool

这里的池指的是"PHP进程池",PHP允许同时启动多个池,每个池使用不同的配置,各个池之间尊重彼此的主权领土完整,互不干涉内政

"Pool对象(线程池)"是多个 Worker 对象的容器,同时也是它们的控制器。它是对 Worker 功能的高层抽象,包括按照 pthreads 需要的方式来管理应用的功能

<?phpclass 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.phphttp://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.htmlhttp://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/40061238121http://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

 

PHP Web System Optimization(undone)