缘由
大家都知道我们做一个客服系统,是基于websocket长连接服务的。在这些长连接服务进程中,会对数据库有些操作。建议尽量在这些长连接服务中,少查询数据,能用缓存都用缓存。我们都知道数据库服务保持连接是有时间限制的,过了时间在操作数据库会出现错误:MySQL server has gone away。这里就要用到数据库重连机制来实现了
我们的客服系统使用的MVC框架是Yii2,大家都知道我一直使用这个框架,其他的框架基本不怎么熟悉。那我们就看看基于Yii2 如何实现数据库断开重连机制
解决思路
在很多编程语言里面有一个概念:连接池。在世界上最好的语言php世界里目前这块成熟的还是比较少的,这个主要是php的以前的应用场景主要是web,都是短连接。在Java语言中数据库连接池会定期发送一些查询语句到数据库进行查询维护着数据库连接(由于个人Java水平有限,如有误请大家指正)。
基于上面的思想,借鉴前面的思想。由于连接池也是单独的独立服务,php不具备这样的条件,那我们改变下思路,在执行SQL报错进行数据库重新连接在执行一次就可以了。
实现过程
实现代码
如下图的上线一个SQL执行类,在出错的时候进行再次执行
handleException($e)){
return parent::execute();
}
throw $e;
}
}
/**
* 重新处理一下.
* @param string $method
* @param null $fetchMode
* @return mixed
* @throws Exception
*/
protected function queryInternal($method, $fetchMode = null)
{
try{
return parent::queryInternal($method, $fetchMode);
}catch(\Exception $e) {
if( $this->handleException($e) ){
return parent::queryInternal($method, $fetchMode);
}
throw $e;
}
}
/**
* 利用$this->retry属性,标记当前是否是数据库重连
* 重写bindPendingParams方法,当当前是数据库重连之后重试的时候
* 调用bindValues方法重新绑定一次参数.
*/
protected function bindPendingParams()
{
if ($this->retry) {
$this->retry = false;
$this->bindValues($this->params);
}
parent::bindPendingParams();
}
/**
* 处理执行sql时捕获的异常信息
* 并且根据异常信息来决定是否需要重新连接数据库
* SQL Error (2013): Lost connection to MySQL server at 'waiting for initial communication packet', system error: 0
* SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
* Error while sending QUERY packet. PID=24450 The SQL being executed was
* 但是实际使用中发现,由于Yii2对数据库异常进行了处理并封装成\yii\db\Exception异常
* 因此2006错误的错误码并不能在errorInfo中获取到,因此需要判断errorMsg内容
* @param \Exception $e
* @return bool true: 需要重新执行sql false: 不需要重新执行sql
*/
private function handleException(\Exception $e)
{
//如果不是yii\db\Exception异常抛出该异常或者不是MySQL server has gone away
$offset_1 = stripos($e->getMessage(),'MySQL server has gone away');
$offset_2 = stripos($e->getMessage(),'Lost connection to MySQL server');
$offset_3 = stripos($e->getMessage(),'Error while sending QUERY packet');
if( ( $e instanceof \yii\db\Exception) == true || ($offset_1 || $offset_2 || $offset_3 )){
$this->retry = true;
//将pdo设置从null
$this->pdoStatement = null;
$this->db->close();
$this->db->open();
return true;
}
return false;
}
}
配置更改
在数据库配置的地方更改 执行类为上面新写的,如下图示意'chat_db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=chat_db',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'commandClass' => 'common\components\Command',//这一句是重点
],
最后
我们的客服系统已经平稳运行了差不多6个月了,期间长连接服务也是非常稳定的。撒花。