本篇重点是Medoo的断线重连和主从读写,没啥swoole的内容
-----------------------------------------下面是一张图片-------------------------------------------------
-----------------------------------------图片结束-------------------------------------------------
描述:
用swoole 的相信都遇到过这个问题,我的解决方案是找到Medoo代码中所有sql执行的最终位置,
然后再那里捕获到 2006 的异常, 然后进行重连
最终发现是下面这个函数
public function exec($query, $map = [])
{
try{
if ($this->debug_mode)
{
echo $this->generate($query, $map);
$this->debug_mode = false;
return false;
}
if ($this->logging)
{
$this->logs[] = [$query, $map];
}
else
{
$this->logs = [[$query, $map]];
}
@$statement = $this->pdo->prepare($query);
if ($statement)
{
foreach ($map as $key => $value)
{
$statement->bindValue($key, $value[ 0 ], $value[ 1 ]);
}
$statement->execute();
$this->statement = $statement;
return $statement;
}
} catch(\PDOException $e){
// 这段代码是我写得
if(in_array($e->errorInfo[1], [2006,2013])){ // 重新连接
$useDbConfig = require(ROOT_PATH . '/config/common.php');
if(strpos($query, 'SELECT') === 0){ // 主从区分, 目前这个区分个人感觉不是很好,如果有好建议的大神, 还请赐教,我先谢谢了
// getDbConfig() 这个函数实现在下面
self::$_readInstance = self::getInstance(getDbConfig($useDbConfig, 'slave'),'slave', $this); // 只读
return self::$_readInstance->exec($query, $map);
}else{
self::$_instance = self::getInstance(getDbConfig($useDbConfig, 'default'),'default', $this); // 写
return self::$_instance->exec($query, $map);
}
}
}
return false;
}
function getDbConfig($useDbConfig, $key)
{
$defaultDb = $useDbConfig['mysql'][$key];
$dbConf = [
'database_type' => 'mysql',
'database_name' => $defaultDb['db'],
'server' => $defaultDb['host'],
'username' => $defaultDb['user'],
'password' => $defaultDb['pwd'],
'port' => $defaultDb['port'],
'charset' => 'utf8',
'option' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,//需要将错误处理模式,变为异常模式, 这个可以让其抛出2006的错误
PDO::ATTR_STRINGIFY_FETCHES => false,// 提取的时候将数值转换为字符串。
PDO::ATTR_EMULATE_PREPARES => false, // 启用或禁用预处理语句的模拟。
PDO::ATTR_PERSISTENT => true, // 持久化链接
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, // 使用缓冲查询
]
];
return $dbConf;
}
个人比较喜欢贴代码说话如下:
// 记得要将构造函数私有化, 下面是单例实现, 这段代码贴到Medoo里面即可
private static $_instance; // 写对象
private $_instanceVersion; // 写对象的版本号
private static $_readInstance; // 只读对象
private $_readInstanceVersion; // 只读对象版本号
/**
* 获取Medoo的单例
* @param $nowConnct : 调用的当前Medoo对象, 防止当前进程的mysql连接可用的时候,又重新PDO连接; 利用对象版本号作为区分
*/
public static function getInstance($dbConf, $key, $nowConnct=NULL)
{
if($key == 'default'){
if(!is_null($nowConnct)){ // 这里只有 断线重连才会触发(errorCode:2006)
// nowVersion == newVersion : 当前的和最新的版本号一致,那么重新连接然后更新版本号;
if($nowConnct->_instanceVersion == self::$_instance->_instanceVersion){
self::$_instance = NULL; // 这个设置空,就会自动重新连接
}
}
if(is_null(self::$_instance)) {
self::$_instance = new self($dbConf);
// getMillisecond() 我自己定义的一个获取毫秒时间戳的函数,你们可以用你们自己设计的
self::$_instance->_instanceVersion = getMillisecond(); // 更新版本号
__accessLog('Version : ' . self::$_instance->_instanceVersion);
}
return self::$_instance;
}
elseif ($key == 'slave') {
if(!is_null($nowConnct)){
if($nowConnct->_readInstanceVersion == self::$_readInstance->_readInstanceVersion){
self::$_readInstance = NULL;
}
}
if(is_null(self::$_readInstance)) {
self::$_readInstance = new self($dbConf);
self::$_readInstance->_readInstanceVersion = getMillisecond();
}
return self::$_readInstance;
}
}
这里讲一下对象版本号的作用, 就是这两个属性:
_instanceVersion;_readInstanceVersion (其实是一样的作用)
websocket 中我发现下面的现象:
多次连接的异常是:
系统连续发出三个查询: a b c
a 发现断了, 然后它就重连在查询, 然后完毕
b 也发现断了, 然后它也重连在查询, 然后完毕
c 也发现断了, 然后它也重连在查询, 然后完毕
问题是: 这里不应该b,c 也重连, 因为a 拿到了最新的连接, 应该把这个连接直接给b,c即可;因为单例了啊;
解决方案:
需要保证 a 重连的时候, b,c的连接也能拿到最新的
然后我设计了对象版本号, 原理如下:
a重连的时候会将单例里面的对象变成最新的, b,c执行sql的时候会发现2006了,此时他们肯定走异常重连处理;如果我能把a产生的对象给他们就ok了
如果我给每次的对象都加上版本号使用的时候就可以:
a,b,c 同时执行查询的时候他们的对象都是A对象本号是100: 简写 A(100)
执行查询的时候 a 查询发现2006, 然后走了重连
重连之前我加了版本好判断: a 查询用的是 A(100), 单例里面是 A(100)
对比发现是同一个对象, 这时候 就产生一个新的 对象 A(101) 返回给 a 查询去使用
这时候 b 查询开始了:
b 查询带着 A(100) 他发现2006了, 走重连;
重连之前判断下版本号, 发现: b 查询用的是 A(100) , 单例里面已经是 A(101)
对比, 发现 版本号不一致; 就表示, 有新的 对象可用, 那么 b 查询就不重连连接了,直接拿着 a 查询产生的 A(101) 使用就可以了
结语:
具体的代码实现可以看上面的单例实现就可以了
第一次设计, 如果有更好的方案,或者有些别的框架已经实现的思路也请各位看官告知我, 谢谢大佬先;
小弟邮箱: [email protected]