Medoo+swoole組合

前言

本篇重點是Medoo的斷線重連和主從讀寫,沒啥swoole的內容

Medoo

  1. medoo是什麼 : 我覺得就是個封裝好了PHP使用PDO操作數據庫的一個框架;
  2. github地址
  3. medoo使用文檔

swoole斷線重連描述

-----------------------------------------下面是一張圖片-------------------------------------------------
swoole關於mysql斷線重連的說明
-----------------------------------------圖片結束-------------------------------------------------

描述:

用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]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章