前言
本篇重點是Medoo的斷線重連和主從讀寫,沒啥swoole的內容
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;
}
PDO 連接的屬性和option如下(getDbConfig()函數聲明)
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的單例和websocket中使用遇到的, 單例失效問題
個人比較喜歡貼代碼說話如下:
// 記得要將構造函數私有化, 下面是單例實現, 這段代碼貼到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]