1. 程式人生 > >php多程序單例模式下的 MySQL及Redis連線錯誤修復

php多程序單例模式下的 MySQL及Redis連線錯誤修復

問題描述:

前幾天寫了個php常駐指令碼,主要邏輯如下

//跑完資料後休息60秒
$sleepTime = 60;
$maxWorker = 10;
while (true) {
    $htmlModel = new DetailHtmlModel();
    //新抓取的html數目
    $count = $htmlModel->getCount(array(
        array('status', '=', DetailHtmlModel::STATUS_UNDEAL)
    ));
    if ($count > 0) {
        //將抓取的html資料放入佇列
Queue::putHtmlData(); } $queueLength = Queue::getHtmlQueueLength(); if ($queueLength > 0) { //啟動的worker數目,限制啟動的個數 $workerCount = min($maxWorker, ceil($queueLength / 10)); runWorker($workerCount); $sleepTime = 60; } else { //無資料的話每次多停5秒 $sleepTime
+= 5; } unset($htmlModel); sleep($sleepTime); }

看程式碼知道,我根據待處理的資料量啟動最多10個的worker去處理資料,每個worker是一個程序,但跑起來後遇到兩個錯誤:
一個是mysql的:MySQL server has gone away
另一個是redis的:Uncaught exception ‘RedisException’ with message ‘read error on connection’

原因:

出現MySQL server has gone away 常見的原因就是連線時間超過了mysql設定的wait_timeout值,這時mysql會主動關掉連線,預設是8小時。但是我是啟動指令碼就出現這個錯誤,而且使用連線前都先做了ping,不可能是連線超時了,又看了mysql官網對這個錯誤可能原因的說明(

原文)裡面有這麼一句:

You can also encounter this error with applications that fork child processes, all of which try to use the same connection to the MySQL server. This can be avoided by using a separate connection for each child process.

很多公司程式碼在建立mysql和redis的連線例項時,採用的都是單例模式,我們也一樣。也就是說無論外部new了多少例項,只要控制代碼存在就會被返回,而不是重新建立連線例項。剛好我又啟動了多個程序去處理資料,然後每個程序又使用同一個連線例項,就導致了報錯

解決辦法:

通常只有在cli執行模式下才有可能出現超時的情況,所以在構建mysql單例控制代碼的key時使用getmypid()加入程序id,這樣就能把每個程序使用的連線例項分隔開
mysql:

private static function _getHandleKey($params) {
    //解決多程序會保持同一個連線的錯誤
    if (PHP_SAPI === 'cli') {
       $params['pid'] = getmypid();
    }
    ksort($params);
    return md5(implode('_' , $params));
}

redis也使用同樣方法解決問題