多进程编程mysql超时定位

背景介绍

年前对下单中的异步出单队列进行了重构,用主进程fork子进程处理消息队列。因为可以通过控制子进程的数量,提高该消息队列的处理效率。由于一开始,都只是先上消息量不大的队列,所以都只fork了一个子进程。在生产上偶发子进程丢失mysql连接,一开始认为是mysql超时了,修改了代码,改为每次拿到新消息都示例化一个新的model去拿数据,并且在代码上加入了异常告警,后面仍出现丢失MySQL连接。后面发现是在父进程一开始就示例化了mysql连接,并且在php的tp3.2框架中,对model做了静态单例。

第一次遇到mysql超时

身为一个弱到爆的开发,我一开始认为是mysql超时了,为了快速把生产问题处理掉,就在每次拿到消息时,实例化model。并以为这样就处理掉了。

第二次遇到

过了几天,子进程丢失mysql连接的事故依然发生。这次我在代码上加入了异常告警,并且开始想着如果把这个问题处理掉。
查看mysql超时设置,发现mysql的超时时间为86400,但是生产的进程每天凌晨都会进行一次重启,显然,并不是mysql连接超时导致的。
为了找出问题,将生产的代码,写了简化版本在测试环境跑了起来。
以下为示例代码:

model =  new AModel();
    }

    //跑这个方法运行
    public function run()
    {
        for ($i = 0; $i < 3; $i++) {
            $nPID = \pcntl_fork();//创建子进程
            if ($nPID == 0) {
                $this->work();
                exit(0);
            }
        }

        $n = 0;
        while ($n < $this->procNum) {
            $nStatus = -1;
            $nPID = \pcntl_wait($nStatus);
            if ($nPID > 0) {
                ++$n;
            }
        }
      
        exit;
    }


    //起进程
    protected function work()
    {
        $MsgData = "";
        while (true) {
            usleep(10000);
            \Org\Util\MsgQ::init();
            $ret = MsgQ::BlockSubsribe('testMysqlConnect', $MsgData);
            if ($ret === false) {
                continue;
            }
            $this->doSomething();
        }
    }

    protected function doSomething()
    {

    }

}

use Think\Model;
class AModel extends Model{

    protected $tablePrefix = 't_';
    protected $tableName = 'underwrite_tmp';

    protected $dbName = "underwrite";
    //数据库配置
    protected $connection = "UNDERWRITE_DB_CONFIG";
}

在测试环境跑了大概有12小时候之后,果然问题复现了。
这里通过netstat -anp | grep pid 命令查看进程监听的端口,发现问题发生的时候,mysql的连接已经不见了。

mysql连接关闭的原因

  • mysql 的server端主动关闭,(超过了超时时间)
  • mysql的server端主动关闭,mysql发现tcp的包时序出现了异常(即多个进程用了同一个连接)
  • mysql的client端关闭,资源被回收了(进程退出,变量被unset)

对应的

  • 因为mysql的系统设置 超时时间都是86400,且进程在每天都会被重启一次,所以不是因为超时关闭的。
  • 因为主进程后面只做对子进程的监听,并没有尝试与mysql进行连接,所以应该不是主进程和子进程复用同一个连接导致的
  • 阅读了tp3.2的db实现的底层源码,发现tp在db连接上,做了静态单例,查了php的fork,发现php直接调用了系统的fork函数。
/* {{{ proto int pcntl_fork(void)
   Forks the currently running process following the same behavior as the UNIX fork() system call*/
PHP_FUNCTION(pcntl_fork)
{
    pid_t id;

    id = fork();
    if (id == -1) {
        PCNTL_G(last_error) = errno;
        php_error_docref(NULL, E_WARNING, "Error %d", errno);
    }

    RETURN_LONG((zend_long) id);
}

出现连接超时的原因

因为有构造函数的存在,导致一开始,父进程就有了mysql连接的静态单例,直接用了linux的fork,可以直接认为这时候子进程和主进程的变量都是一致。因而,子进程用了父进程的MySQL连接,而且,因为做了静态单例,所以不管怎么实例化,都是得到同一个mysql连接。

解决方法

主进程一进来,只做fork子进程的工作,别的连接都不处理。让子进程自己去实例化各个资源即可

你可能感兴趣的:(多进程编程mysql超时定位)