很久很久以前,我也是因为工作上的bug,研究了php mysql client的连接驱动mysqlnd 与libmysql之间的区别php与mysql通讯那点事,这次又遇到一件跟他们有联系的事情,mysqli与mysql持久链接的区别。写出这篇文章,用了好一个多月,其一是我太懒了,其二是工作也比较忙。最近才能腾出时间,来做这些事情。每次做总结,都要认真阅读源码,理解含义,测试验证,来确认这些细节。而每一个步骤都需要花费很长的时间,而且,还不能被打断。一旦被打断了,都需要很长时间去温习上下文。也故意强迫自己写这篇总结,改改自己的惰性。
在我和我的小伙伴们如火如荼的开发、测试时发生了“mysql server too many connections”的错误,稍微排查了一下,发现是php后台进程建立了大量的链接,而没有关闭。服务器环境大约如下php5.3.x 、mysqli API、mysqlnd 驱动。代码情况是这样:
//后台进程A/*配置信息'mysql'=>array( 'driver'=>'mysqli',// 'driver'=>'pdo',// 'driver'=>'mysql', 'host'=>'192.168.111.111', 'user'=>'root', 'port'=>3306, 'dbname'=>'dbname', 'socket'=>'', 'pass'=>'pass', 'persist'=>true, //下面有提到哦,这是持久链接的配置 ),*/$config=Yaf_Registry::get('config');$driver = Afx_Db_Factory::DbDriver($config['mysql']['driver']); //mysql mysqli$driver::debug($config['debug']); //注意这里$driver->setConfig($config['mysql']); //注意这里Afx_Module::Instance()->setAdapter($driver); //注意这里,哪里不舒服,就注意看哪里。$queue=Afx_Queue::Instance();$combat = new CombatEngine();$Role = new Role(1,true);$idle_max=isset($config['idle_max'])?$config['idle_max']:1000;while(true){ $data = $queue->pop(MTypes::ECTYPE_COMBAT_QUEUE, 1); if(!$data){ usleep(50000); //休眠0.05秒 ++$idle_count; if($idle_count>=$idle_max) { $idle_count=0; Afx_Db_Factory::ping(); } continue; } $idle_count=0; $Role->setId($data['attacker']['role_id']); $Property = $Role->getModule('Property'); $Mounts = $Role->getModule('Mounts'); //............ unset($Property, $Mounts/*.....*/);}
从这个后台进程代码中,可以看出“$Property”变量以及“$Mounts”变量频繁被创建,销毁。而ROLE对象的getModule方法是这样写的
//ROLE对象的getModule方法class Role extends Afx_Module_Abstract{ public function getModule ($member_class) { $property_name = '__m' . ucfirst($member_class); if (! isset($this->$property_name)) { $this->$property_name = new $member_class($this); } return $this->$property_name; }}//Property 类class Property extends Afx_Module_Abstract{ public function __construct ($mRole) { $this->__mRole = $mRole; }}
可以看出getModule方法只是模拟单例,new了一个新对象返回,而他们都继承了Afx_Module_Abstract类。Afx_Module_Abstract类大约代码如下:
abstract class Afx_Module_Abstract{ public function setAdapter ($_adapter) { $this->_adapter = $_adapter; }}
类Afx_Module_Abstract中关键代码如上,跟DB相关的,就setAdapter一个方法,回到“后台进程A”,setAdapter方法是将Afx_Db_Factory::DbDriver($config['mysql']['driver'])的返回,作为参数传了进来。继续看下Afx_Db_Factory类的代码
class Afx_Db_Factory{ const DB_MYSQL = 'mysql'; const DB_MYSQLI = 'mysqli'; const DB_PDO = 'pdo'; public static function DbDriver ($type = self::DB_MYSQLI) { switch ($type) { case self::DB_MYSQL: $driver = Afx_Db_Mysql_Adapter::Instance(); break; case self::DB_MYSQLI: $driver = Afx_Db_Mysqli_Adapter::Instance(); //走到这里了 break; case self::DB_PDO: $driver = Afx_Db_Pdo_Adapter::Instance(); break; default: break; } return $driver; }}