php cli swoole mysql_[了解实践]Swoole、PHP与MySQL:连接池,swoole扩展实现真正的PHP数据库连接池。...

背景:swoole的出现,包括PHP出现前,在新浪企业邮箱就有基于Sun Solaris 系统上面用c++写Mysql的长连接,那时候的长连接是基于RPC实现,对mysql那一端形成一个远程过程的调用,通过XDR数据结构进行解析mysql传来的数据项(RPC也为sun最新提出并后来在linux上默认支持),也就是说像用户登录验证这一块用Mysql的长连接来实现,提高其效率运行相当稳定,后面这个系统迁移到了FreeBSD后,出现了mysql长连接的服务经常出现假死,也就是说进程还在,但是已经连接不上mysql了,重新启动这个RPC服务又好了,原因未知,当时我对c++不了解(现在也不太了解,只听说要看是否形成coredump啥的),当年我还写过一个判断死了就杀死,重启动,判断的程序也老半天回不来,于是我又改成了一个多进程,如果超时没有回来,就干掉那个进程,重启Rpc服务,再后来,这套C++的cgi被替换成了php,再后来基于FreeBSD的系统迁移到了Linux,也就是现在一直在linux上,linux也就强大了起来,回想起来,当年一个登录服务如此极致,现在都变成了直接查询mysql了,这个长连接技术有还有用吗?我只能说对有上千台上万台的服务器可能有用,能节省一定的机器成本罢。但是,追求技术永无止境,需要有这样的一些东西来繁荣我们这个PHP的市场,长连接这个话题不再是Java做成了连接池,像c++也能做成连接池,像腾讯广平就有c++团队还有写cgi实现长连接Mysql服务,据说前二年吧更多关注了H5,像实时技术,比如Tail技术在web上的实现,有转向nodejs的趋势,而此时的PHP拿不出这样的技术,是很危险的,有了swoole起到填补作用,我更多的是觉得官方应该重视这个技术,而不是形成一个扩展,像H5的来到,像websocket的进入,这些东西对于Node来讲,从前端向后端的统一,而PHp呢?没有谁来解决,那么从用户角度来讲,开发者用户的流失或迁移,对PHP本身也是一个损失,但我还是说PHP是最好的语言没有之一,期望其能伴随潮流,与时俱进。

PHP的数据库连接池一直以来都是一个难题,很多从PHP语言转向Java的项目,大多数原因都是因为Java有更好的连接池实现。PHP的MySQL扩展提供了长连接的API,但在PHP机器数量较多,规模较大的情况下,mysql_pconnect非但不能节约MySQL资源,反而会加剧数据库的负荷。

假设有100台PHP的应用服务器,每个机器需要启动100个apache或fpm工作进程,那每个进程都会产生一个长连接到MySQL。这一共会产生1万个My SQL连接。大家都知道MySQL是每个连接会占用1个线程。那MYSQL就需要创建1万个线程,这样大量的系统资源被浪费在线程间上下文切换上。而你的业务代码中并不是所有地方都在做数据库操作,所以这个就是浪费的。

连接池就不同了,100个worker进程,公用10个数据库连接即可,当操作完数据库后,立即释放资源给其他worker进程。这样就算有100台PHP的服务器,那也只会创建1000个MySQL的连接,完全可以接受的。

以前确实没有好的办法来解决此问题的,现在有了swoole扩展,利用swoole提供的task功能可以很方便做出一个连接池来。

代码如下:

$serv = new swoole_server("127.0.0.1", 9508);

$serv->set(array(

'worker_num' => 100,

'task_worker_num' => 10, //MySQL连接的数量

));

function my_onReceive($serv, $fd, $from_id, $data)

{

//taskwait就是投递一条任务,这里直接传递SQL语句了

//然后阻塞等待SQL完成

$result = $serv->taskwait("show tables");

if ($result !== false) {

list($status, $db_res) = explode(':', $result, 2);

if ($status == 'OK') {

//数据库操作成功了,执行业务逻辑代码,这里就自动释放掉MySQL连接的占用

$serv->send($fd, var_export(unserialize($db_res), true) . "\n");

} else {

$serv->send($fd, $db_res);

}

return;

} else {

$serv->send($fd, "Error. Task timeout\n");

}

}

function my_onTask($serv, $task_id, $from_id, $sql)

{

static $link = null;

if ($link == null) {

$link = mysqli_connect("127.0.0.1", "root", "root", "test");

if (!$link) {

$link = null;

$serv->finish("ER:" . mysqli_error($link));

return;

}

}

$result = $link->query($sql);

if (!$result) {

$serv->finish("ER:" . mysqli_error($link));

return;

}

$data = $result->fetch_all(MYSQLI_ASSOC);

$serv->finish("OK:" . serialize($data));

}

function my_onFinish($serv, $data)

{

echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL;

}

$serv->on('Receive', 'my_onReceive');

$serv->on('Task', 'my_onTask');

$serv->on('Finish', 'my_onFinish');

$serv->start();

看完上面的,看完了,觉得真的很厉害。毕竟,现在来说可能是还没有真正在PHP使用数据库连接池的大应用。今天终于准备实验实验。

在开始之前,还是说一下测试环境吧:OS CentOS 6.4 x86;php 5.3.17;MySQL 5.5.28;Swoole 1.6.8。

首先,我们当然要把代码写好。下面是服务端代码,很多是参考了上面文章里面的,当然也有自己的内容。

$serv = swoole_server_create('127.0.0.1', 3305, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);//端口3305

swoole_server_set($serv, array(

'worker_num' => 2,      //worker线程的数量

'task_worker_num' => 1, //MySQL连接的数量

));//作为小型测试,参数调得比较小

//这里有一个守护进程化的参数,由于是实验,没有加入

function my_onReceive($serv, $fd, $from_id, $data){

//执行查询

$result = $serv->taskwait($data);

if ($result !== false) {

swoole_server_send($serv, $fd, $result);

return;

} else {

swoole_server_send($serv, $fd, "Error. Task timeout\n");

}

}

function my_onTask($serv, $task_id, $from_id, $sql){

static $link = NULL;

if ($link == NULL) {

$link = mysqli_connect('localhost', 'user', 'pw', 'db');

//localhost=>UNIX Socket , IP地址=>TCP/IP

}

$result = $link->query($sql);

if ($result === false) {

swoole_server_finish($serv, 'b:0;');//语句运行失败,这是serialize后的false,下同理

return;

}

if ($result === true){

swoole_server_finish($serv, 'b:1;');//写入操作成功

return;

}

$data = $result->fetch_all(MYSQLI_ASSOC);

swoole_server_finish($serv, serialize($data));

}

function my_onFinish($serv, $data){

//这次实验就没有写东西了

//但是必须有这个函数定义

//其实可以写日志什么的吧

}

swoole_server_handler($serv, 'onReceive', 'my_onReceive');

swoole_server_handler($serv, 'onTask', 'my_onTask');

swoole_server_handler($serv, 'onFinish', 'my_onFinish');

//上面是设置回调函数

swoole_server_start($serv);

swoole_event_wait();//实验环境是PHP5.3,所以需要这个函数进行事件轮询;5.4+就不需要了

如果把上面的连接池代码和Rango的相比较,会发现我的对于错误的部分处理很少。其实这个时候用CLI运行这个连接池,然后使用Telnet已经就可以直接测试效果了。但是不管怎么说这个东西还是要应用在PHP上面的,我简单地写一下PHP方面的代码。

$link=new swoole_client(SWOOLE_SOCK_TCP,SWOOLE_SOCK_SYNC);//TCP方式、同步

$link->connect('127.0.0.1',3305);//连接

$link->send('SELECT * FROM `table`');//执行查询

$res=unserialize($link->recv());

if(!res){

echo 'Failed!';

}

else{

print_r($res);

}

$link->close();

//上面的是最简单的测试,下面可以简单地改写成函数

function dbcp_query($sql){

$link=new swoole_client(SWOOLE_SOCK_TCP,SWOOLE_SOCK_SYNC);//TCP方式、同步

$link->connect('127.0.0.1',3305);//连接

$link->send($sql);//执行查询

return unserialize($link->recv());

//swoole_client类析构时会自动关闭连接

}

现在可以运行了,本次实验是成功的。但是如果使用dbcp_query()这个函数,每次调用都要发起一次TCP连接,执行的语句多了,肯定出问题。这个时候我们就可以把它封装成一个类了,单纯实现这个会比较的简单,但是打出来要点时间,这里就不写了。

最后:今天做的是数据库连接池的实现。从上面的代码我们可以看见,程序与连接池之间的数据交换是使用php序列进行的。这里会有两次的serialize、unserialize,绝对也是一个开销。Rango的文章里面有说到“MySQL是每个连接会占用1个线程……大量的系统资源被浪费在线程间上下文切换上……不是所有地方都在做数据库操作,所以这个就是浪费的。”再看看他那篇文章的假设:“假设有100台PHP的应用服务器,每个机器需要启动100个apache或fpm工作进程。”这肯定不是一个小项目,确实就适合用连接池了。写的东西是用来练手或者解闷儿的?常规方法已经可以了。不要忘了一点:程序与连接池的交互我们应该还是用Swoole实现的,Swoole可是一个TCP/UDP扩展。而Swoole只能运行在Linux平台上面,但是Linux平台上的MySQL是可以用UNIX Socket通讯的。

P.S.:找个时间给epdb改写一个支持数据库连接池的版本。

来自:http://rango.swoole.com/archives/265

http://bokjan.com/prog/php-db-conn-pool-with-swoole.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值