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也使用同样方法解决问题

 

posted @ 2018-03-27 16:43 过往云烟- 阅读(...) 评论(...) 编辑 收藏