持续的思考

php 与 mysql proxy

php与mysql

    php与mysql相生相爱,和linux nginx/apache组成威力无比的lnmp/lamp组合,在互联网界打下了一大片江山。但人红事非多,有说php本身的性能比较低下,关于这个问题,大都是跟风者的无脑喷子而已,这里不讨论了,也有人说php没有mysql连接池,这明显是想挑拔php和mysql已合体的关系嘛,然并卵,不过这里作者就要和各位看官好好来讨论一下php与mysql连接池了

connect与pconnect

    mysqli 是目前用的比较多的 php内核自带的mysql扩展,在php里通过mysqli和mysql数据库连接,有两种方式:connect, pconnect, 他们有何异同,一起来简单分析一下:
            
    connect:   每个进程在每次请求结束之后,会关闭连接    
    pconnect:  每个进程在请求结束之后,不关闭连接,可以再当前进程内复用

    connect会产生几个弊端:
        1) 每次请求,都要重新连接,影响效率 
        2) 频繁的连接、关闭,会产生大量的TIME_WAIT, 大高并发的场景会迅速影响系统的连接能力(不仅仅是mysql,而是影响整个系统所有的服务)
    pconnect会产生的弊端:
        1)  持久连接如果产生无法释放数据表锁,会导致相同连接的脚本将会被持久的阻塞,进而拖跨整个应用
        2)  如果连接数超过mysql连接数,会导致mysql gone away

那究竟选用哪种方式, 这里推荐用pconnect, 原因就是效率,但如何来避免pconnect的弊端呢?那得从mysql的执行流程说起.

mysql的运行流程

    在这动不动高并发,单机百万连接的年代,mysql连接数还是有限的,这是因为mysql本身的机制造成的,mysql是一个单进程的服务,对于每一个请求都是用线程来响应的。这就需要一个连接器来处理新用户的请求、相应,以及销毁。所以连接数在mysql中是有限的(my.cnf里有相关的配置),回过头来看php, 我们一般都配置有几百个fpm的进程,每个fpm进程都和mysql保持一个持久连接,当我们只有一两台机器的时候,一般没有什么问题 (fpm进程总数小于mysql最大连接数就ok),但随着我们的业务大增,php的机器增加到10台机器以上,每台机器都和mysql pconnect,那么fpm进程数就远远大于mysql的最大连接数了,这个时候就会出现问题,针对这类的问题,通常的解决方案是引入连接池

mysql 连接池

    上面提到,当有众多机器和mysql进行pconnect,这导致超过了mysql的最大连接数,但实际的场景中,并不是每个fpm都同时在工作,真正的并发连接很少,所以这引入了连接池的新技术,顾名思义,连接池就是预先建立好足够多的mysql连接, 当有需要的时候,从连接池里拿出一个空闲连接处理,处理完成之后,把连接放回池子,供下次使用, 而php本身的执行流程导致做不了这件事,需要依赖于第三方的插件, 这个插件称之为mysql proxy

mysql proxy

    mysql proxy需要作两方面的事情:
    1) 对于连接池需求方(php),能维持海量的连接数
    2) 能预先建立好和mysql的持久连接

    那这需要proxy首先是一个长驻内存的服务,并且能维护大量的连接数,由于php运行流程,导致php不适合做这样的服务,所以就出现文章开头的吐嘈,但swoole的出现,成功解决了这个问题。所以我们用swoole来实现一个简单可靠的mysql proxy

show me the code:
    proxy.php
             <?php

$opt = getopt("d", [
"ip::",
"port::",
"pool::"
]);
print_r($opt);
if (empty($opt['ip']) || empty($opt['port']) || empty($opt['pool'])) {
echo "examples:  php proxy.php --ip=0.0.0.0 --port=9501 --pool=4 -d" . PHP_EOL;
return;
}
if(!extension_loaded('swoole')) {
echo 'pls install swoole extension, url: https://github.com/swoole/swoole-src'.PHP_EOL;
return;
}
$config = null;
$pdo = null;
$serv = new swoole_server($opt['ip'], $opt['port']);
$daemonize = 0;
if(isset($opt['d'])) {
$daemonize = 1;
}
$serv->set([
'worker_num'=>4,
'task_worker_num'=>$opt['pool'],
'daemonize' => $daemonize
]);

$serv->on('start', function($serv) {
global $opt;
    swoole_set_process_name("mysql proxy runing tcp://{$opt['ip']}:{$opt['port']}, start:".date("Y-m-d H:i:s").", pid:".$serv->master_pid);
});

$serv->on('managerStart', function($serv) {
   swoole_set_process_name("mysql proxy manager process, pid:".$serv->manager_pid);
});

$serv->on('workerStart', function($serv, $workerId) {
if($workerId <4) { //worker id
swoole_set_process_name("mysql proxy worker process, pid:".$serv->worker_id);
    } else {
        swoole_set_process_name("mysql proxy pool process, pid:".$serv->worker_id);
global $config;
$config = include_once(__DIR__.DIRECTORY_SEPARATOR.'config.php');
include_once(__DIR__.DIRECTORY_SEPARATOR.'zpdo.php');
global $pdo;
$pdo = new zpdo($config);

    }
});

$serv->on('receive', function($serv, $fd, $fromId, $data) {
if (empty($data)) {
return;
    }
$serv->task([$fd, $data]);  //数据转到task进行处理
});

$serv->on('task', function($serv, $taskId, $fromId, $_data) {
list($fd, $sql) = $_data;
    swoole_set_process_name("mysql proxy pool process, pid:".$serv->worker_id." sql:{$sql}");
global $pdo;
$ret = $pdo->fetchBySql($sql);
$serv->send($fd, json_encode($ret));
    swoole_set_process_name("mysql proxy pool process, pid:".$serv->worker_id);
});

$serv->on('finish', function() {});

$serv->start();

    
    client.php
             <?php
$client = new swoole_client(SWOOLE_TCP|SWOOLE_KEEP);
$client->connect('127.0.0.1', 9501);
$client->send('select * from test.user');
echo $client->recv();


github地址: https://github.com/shenzhe/zproxy

    由于swoole本身可能维护超大量的连接数,所在前面有再多的机器,都没有任何问题。而且还要以在worker进程内通过异步mysql,达到更大的并发处理能力。

后记:
    当然上面只是一个proxy的雏形,比如连接池的动态扩展,事务的处理,很多异常的处理都没有展现出来,这里只是抛砖引玉,希望给大家带来一定的收获。
    

2015-12-17
/  标签: php swoole
   
评论
热度(3)