原创

关于php对象池

温馨提示:
本文最后更新于 2019年05月28日,已超过 1,766 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

生命周期

对象池需要从php的生命周期说起,php的应用大部分都是web网站,而大部分web网站使用的都是cgi模式进行运行的,导致php生命周期跟随着请求结束而结束,从而没有对象池的概念

cgi模式的一次请求可以分为以下几步:

1:用户请求

2:web服务器(apache,nginx,iis等)接收请求

3:服务器通过cgi协议调用php,运行php文件

4:php文件处理逻辑,返回数据,php进程 销毁/回收(该次执行的php变量内存等全部回收)

5:web服务器接收数据,返回给用户,web服务器关闭连接

6:用户接收数据,用户关闭连接

在这个过程中,是根本没有对象池概念的,因为php的变量是随着用户的请求而销毁,无法把php的变量留给下一个用户进行执行

这就导致了如果用户1请求,需要new 一个对象,那么用户1请求完毕将会销毁,用户2需要重新再new一个对象,再销毁。。。。。如此反复。

那么,php能实现一个请求进来,结束之后保存对象,然后第二个请求进来的时候,初始化下对象属性(不初始化属性会造成第二个请求用到第一个的垃圾数据),然后让第二个请求直接使用第一个请求new好的对象吗?

php-cli模式

php-cli命令行模式,它和传统cgi不同,cgi是跟web服务器等交互,而web服务器一般是跟使用浏览器的用户交互的
而php-cli是命令行模式,是直接跟开发者交互,由开发者编写程序,然后直接输入

php test.php

进行运行php脚本

为什么要讲php-cli模式呢?

在php-cli模式中,开发者可以编写不中断运行的代码,以及可以自行维护运行php的进程,可以实现一个web服务器和用户交互。

类似于这样:

<?php
//以下为伪代码
class User{
    protected $user;
    public function __construct()
    {
    }

    /**
     * @return mixed
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param mixed $user
     */
    public function setUser($user): void
    {
        $this->user = $user;
    }
}

$phpServerStart=true;//自己实现一个php的web服务器
$user = new User();
while(1){
    //第一次循环,获取到了一个用户请求的信息
    $user = [
        'name'=>'tioncico'
    ];//获取到第一个用户请求我们自己实现的web服务器的数据
    $user->setUser($user);
    echo json_encode($user->getUser());//输出数据到第一个用户,理论上php-cli是跟开发者交互的,echo没法直接输出给用户,该知识点下面将补充
    
    //第二次循环,没有用户请求,继续循环下去
    
//    第三次循环,用户请求
    $user = [
        'name'=>'显示可'
    ];//获取到第一个用户请求我们自己实现的web服务器的数据
    $user->setUser($user);
    echo json_encode($user->getUser());//输出数据到第一个用户,理论上php-cli是跟开发者交互的,echo没法直接输出给用户,该知识点下面将补充
    
    //无限循环下去,不断的获取用户的请求
}

在这份代码中,可以看出:

1:我们在程序一开始,自己实现了一个web服务器

2:先new 了user对象

3:while 1死循环,只要获取到了用户请求,则处理数据

4:获取到了用户1数据,直接填入new好的对象中,并echo回去

5:再次获取到了用户2数据,覆盖之前用户1的对象属性,并echo回去

在这份代码中,为什么$user对象可以复用呢?原因就在于我们使用php-cli模式,用php自己实现了web服务器的部分功能,让php接管了web服务器,这样使得用户请求的生命周期,限制在了while(1)里面,而用户请求结束之后,并不会销毁while(1)外面的变量

知识点直通车:

php运行模式

php网络编程

对象复用

对象复用以及不复用的效率

那么这个时候可能会有人问?new一个对象,多大事啊!给它new不就得了!针对这个问题,我们可以来测试下new一个对象的消耗有多大

新建一个测试脚本:

<?php

class Test
{
    protected $a;

    public function __construct($a)
    {
        $this->a = $a;
    }

    public function setA($a)
    {
        $this->a = $a;
    }

    public function getA()
    {
        return $this->a;
    }
}
$startTime = microtimeFloat();
for ($i = 0; $i < 10000; $i++) {
    $test = new Test($i);
    $test->getA();
}
echo "对象不复用耗时" . (microtimeFloat() - $startTime) . '秒' . PHP_EOL;

$startTime = microtimeFloat();
$test = new Test(0);
for ($i = 0; $i < 10000; $i++) {
    $test->setA($i);
    $test->getA();
}
echo "对象复用耗时" . (microtimeFloat() - $startTime) . '秒' . PHP_EOL;


function microtimeFloat()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

测试结果:

/usr/local/php-7.2.2/bin/php /home/tioncico/PhpstormProjects/test/test.php
对象不复用0.0012578964233398秒
对象复用 0.00068378448486328秒

可看出,在对象复用情况下,效率比不复用情况快了一倍,可能有人会说:缺这么性能算什么?根本看不出啊!

这是因为测试文件的类是最简单的类,如果是复杂点的,例如继承,多重继承构造函数,析构函数,以及triat等等复杂对象,花费的cpu可就不止这些了

为什么复用对象会比不复用快?

这个需要从2方面进行讲解

php实例化对象步骤

如果讲php实例化的底层的话,大家可能听不懂,我也不懂底层,所以本人用通俗的方法讲解下php实例化对象需要做的事情(步骤前后顺序可能有错)

1:实例化对象,检查对象属性,方法,结构等

2:属性初始化

3:对象父类属性初始化

4:构造函数初始化

可以看出,new 一个对象,所做的事跟对象的复杂度有关,比如类继承,类接口实现,检查对象继承接口等是否有错,初始化属性,调用构造函数等等

php 垃圾回收

同样,在回收一个对象时,需要销毁对象的所有属性,父类属性等等,以及调用析构函数等等

如果对象复用,这些操作将都不需要,我们只需要执行一次,即可复用

注:步骤等本人并没有详细了解,只根据本人经验进行模糊以及通俗解释

对象池

在上面的说明中,我们已经知道了对象复用的好处,那么如果我有2个请求同时进来呢?3个?10个?(多请求单进程处理需要php实现异步网络服务器,或者swoole协程网络服务器)

很明显,如果是多个请求同时处理,一个对象是不够用的.

那我们能不能先new 10个,或者100个,1000个,然后每次请求进来就分一个,标记为正在使用,其他请求不能再用这个,请求结束后标记为未使用,等待请求使用?

这个操作,就是对象池。

顾名思义,对象池是一个池子,每次我们需要对象时从里面拿一个,用完再放回去,这样又实现了对象复用,又实现了能同时处理多个请求

对象池的意义

上面我们可能发现了,对象池如果对象太少,比如只有10个,那10个都被人用了,岂不是第11个人没得用了?

答案是对的

那为什么不直接设置10000个,想多少人用就多少人用?

理论上是这样的,但是对象池的意义,就是限制并发的大小,防止服务器负载太高而进行宕机。

例如:

假设没有对象池,也没有对象复用,在传统web模式下,假设进程也有100,10000个,一个请求进来需要消耗1%的cpu

当100个请求进来的时候,cpu已经为100%,勉强全部能运行
而出现101个请求之后,某个请求会因为cpu资源不够,处理将会变慢,直到其他请求处理好一个,腾出1%去处理新的请求

如果当出现200个请求,cpu由于分时调度(尽量使得所有请求处理的时间尽量平均),会使得所有请求平均响应时间慢一倍(如果有一个进程正常响应,那么就说明有几个请求需要慢2倍甚至更多)

再到后面,将会出现只能响应少数请求,其他请求全部超时无法正常响应的宕机情况

上面的cpu资源争夺是其一,其二是消耗内存,如果同时处理太多进程,还有可能造成服务器内存不够。

对象池的意义就在于此:

设定合理的对象池数量,当超出对象池数量时,让请求等待或者直接提示系统繁忙,保证其他请求进行正常响应,保证服务器的运行正常

例如设置了100个对象  第101个请求进来时,使其等待3秒,3秒内如果有对象回收,则直接给101个请求使用,否则3秒后告诉该请求服务器繁忙,请稍后再试,避免出现服务器调度混乱,导致宕机

php什么时候会用到对象池

由于对象池的特性,它只出现在单进程处理多个请求情况而出现(例如java的多线程同时处理),而php中大部分情况是没有的,目前只有在swoole协程中使用较多,或者在php异步网络服务器中使用

正文到此结束
本文目录