首页>新闻动态>尚途学院

PHP程序如何通过协程来实现mysql查询异步化

来源:https://www.icvio.com/ 作者:admin 浏览次数:2590次 发布时间:2016-03-07 09:22:40 收藏:添加收藏



写在文章之初


最近看了很多PHP大牛们的视频,了解到facebook的mysql查询可以进行异步化,从而提高性能。由于facebook实现的比较早,他们不得不对php进行hack才得以实现。现在的php7.0,已经无需hack就可以实现了。


对于一个web网站的性能来说,瓶颈多半是来自于数据库。一般数据库查询会在某个请求的整体耗时中占很大比例。如果能提高数据库查询的效率,网站的整体响应时间会有很大的下降。如果能实现mysql查询的异步化,就可以实现多条sql语句同时执行。这样就可以大大缩短mysql查询的耗时。


so问题来了,php程序异步执行为什么比同步块?


与异步查询相反的时同步查询。通常情况下mysql的query查询都是同步方式。下面我们对两种方式做下对比。对比的例子是,请求两次select sleep(1)。这条语句在mysql服务器端大概耗时1000ms。


下面是程序同步方式的执行流程图:


程序同步执行流程图

上面的流程图,解释一下为:

第一步,向mysql服务器端发送第一次查询请求。大概耗时 1ms

第二步,mysql服务器端返回第一次查询的结果。大概耗时 1000ms

第三步,向mysql服务器再次发送请求。大概耗时 1ms

第四步,mysql服务器端返回第二次查询的结果。大概耗时 1000ms

同步的方式执行两次select sleep(1),大概耗时 2002ms。


而异步方式的执行流程图如下:


程序异步方式执行流程图



我们队异步方式执行进行解释:

第一步,向mysql服务器端发送第一次查询请求。大概耗时1ms

第二步,在等待第一次请求返回数据的同时,向服务器端发送第二次查询请求。大概耗时 1ms

第三步,接受mysql服务器端返回的两次查询请求。大概耗时 1000ms。


对两次分析对比,我们可以得出:


异步查询比同步查询速度快,是因为多条查询语句在服务器端同时执行,大大缩短了服务器端的响应时间。并行一般情况下总比串行快嘛。sql语句执行时间越长,效果越明显。


so,问题又来了,我们该如何实现mysql的异步查询?

要实现异步查询的关键是能把发送请求和接受返回数据分开。正好mysqlnd中提供了这个特性。

在mysqlnd中对应的方法是:

mysqlnd_async_query 发送查询请求

mysqlnd_reap_async_query 获取查询结果

mysqli扩展针对mysqlnd的这个特性做了封装,在调用query方法时,传入MYSQLI_ASYNC即可。


那么,为什么要使用协程?


查看了我们推荐文章的代码实现,是不是感觉写法和平时不一样?一般在项目当中,我们都是以function的形式去相互调用,function中包含了数据库查询。为了保持这个习惯,方便大家使用,因此引入了协程。在php5.5中正好提供了yield和generator,方便我们实现协程。示例代码如下:


<?php
function f1(){
    $db = new db();
    $obj = $db->async_query('select sleep(1)');
    echo "f1 async_query \n";
    yield $obj;
    $row = $db->fetch();
    echo "f1 fetch\n";
    yield $row;
}

function f2(){
    $db = new db();
    $obj = $db->async_query('select sleep(1)');
    echo "f2 async_query\n";
    yield $obj;
    $row = $db->fetch();
    echo "f2 fetch\n";
    yield $row;
}
 
$gen1 = f1();
$gen2 = f2();
 
$gen1->current();
$gen2->current();
$gen1->next();
$gen2->next();
 
$ret1 = $gen1->current();
$ret2 = $gen2->current();
 
var_dump($ret1);
var_dump($ret2);
 
class db{
    static $links;
    private $obj;
    function getConn(){
        $host = '127.0.0.1';
        $user = 'demo';
        $password = 'demo';
        $database = 'demo';
        $this->obj = new mysqli($host, $user, $password, $database);
        self::$links[spl_object_hash($this->obj)] = $this->obj;
        return self::$links[spl_object_hash($this->obj)];
    }
    function async_query($sql){
        $link = $this->getConn();
        $link->query($sql, MYSQLI_ASYNC);
        return $link;
    }
    function fetch(){
        for($i = 1; $i <= 5; $i++){
            $read = $errors = $reject = self::$links;
            $re = mysqli_poll($read, $errors, $reject, 1);
            foreach($read as $obj){
                if($this->obj === $obj){
                    $sql_result = $obj->reap_async_query();
                    $sql_result_array = $sql_result->fetch_array(MYSQLI_ASSOC);//只有一行
                    $sql_result->free();
                    return $sql_result_array;
                }
            }
        }
    }
}
?>


在终端命令行方式执行结果如下:


$time php ./async.php
f1 async_query
f2 async_query
f1 fetch
f2 fetch
array(1) {
    ["sleep(1)"]=>
    string(1) "0"
}
array(1) {
    ["sleep(1)"]=>
    string(1) "0"
}
real    0m1.016s
user    0m0.007s


从结果上我们可以看出执行流程是,先发了两次mysql查询,然后在接受数据库的返回数据。正常情况下,至少需要2000ms才能执行完毕。但是,real 0m1.016s,说明两次查询的耗时只有1016ms。

tips:以上代码只是示例代码,还有一些需要完善的地方。


要注意的地方:


需要注意的是,如果mysql服务器本身负载很大,这种并行执行的方式就不一定是好的解决方法。因为,mysql服务端会为每个链接创建一个单独的线程进行处理。如果创建的线程数过多,会给系统造成负担。因此,最好的解决需求的方式是因地制宜,根据现实情况制定出最有利当前状况的方案才是上佳之选,不一定某一种模式更好于另一种模式。本文向大家提供的是一种选择。


网站建设首选石家庄尚途网络科技有限公司,更多网站优化,网站建设信息请关注:尚途科技,网址:http://www.icvio.com