您的位置 首页 php

记一次PHP在多进程下批量插入mysql的异常

因公司业务发展,建立了数百个微信群,每个群有若干群成员,群成员信息保存在第三方托管平台。现在因业务需求,需要将所有群成员通过第三方提供的接口保存到自己系统(每次请求只能获取一个群的群成员),如果以单进程处理,需要耗费大量时间,所以使用php的pnctl_fork 创建若干个子进程,然后循环批量获取群成员。具体方式为先在自己系统获取到所有的群列表,然后每50个群分为一组,然后遍历每组fork一个进程,每个进程遍历各自的每个群调用接口获取成员。代码如下(laravel框架):

 $group = DB::table('wx_group')->get()->toArray();//所有群信息
$data = array_chunk($group,50);//每50个群分为一组
 $k = 0;
while ($k < count($data)) {
       $pid = pcntl_fork();
       if($pid == -1){
             die('error');
        }
        if($pid > 0){
                cli_set_process_title("wgj_parent");
        }elseif($pid == 0){
                cli_set_process_title("wgj_children");
                //调用第三方接口获取到群成员(代码省略)
                $arr = '群成员二维数组';
                foreach($arr as $i => $j) {
                    $m = [
                        'cluster_id'=>$j['cluster_id'],
                        'wx_first_id'=>$j['wx_first_id'],
                        'nickname'=>$j['nickname'],
                    ];
                    DB::beginTransaction();
                    try{
                        DB::table('wgj_test')->insert($m);
                        DB::commit();
                    }catch (\Exception $e){
                        DB::rollBack();
                        Log::error('群成员处理:'.$e->getMessage());
                    }
                };
                exit(0);//子进程执行完毕,立马退出
         }
        $k++;
}
$n = 0;
//等待所有子进程退出,防止僵尸进程
while ($n < count($data)) {
          $nPID = pcntl_wait($status);
          if ($nPID > 0) {
                echo "{$nPID} exit\n";
                ++$n;
          }
 }
var_dump('success');  

操作完成,查看群成员数量,发现与第三方平台提供的数量不一致,检查错误日志发现mysql提示:PDOStatement::execute(): MySQL server has gone away,百度都说是因为mysql执行时间或执行缓存容量超过了默认值,修改max_allowed_packet=50M 后,问题依然没有解决。

折磨了两个小时后,最后联想到子进程会复制父进程的所有内存变量信息,在fork之前,父进程中已存在mysql的连接信息,那子进程mysql的链接对象也是从父进程复制的,就是说每个子进程用的是同一个mysql链接对象,这儿可能会出现问题(至于为什么,目前仍在研究中)。

于是就做了一个测试:如果在创建子进程之前,父进程不去连接mysql,那么子进程中就不会有mysql的连接对象,子进程想要操作mysql,需要自己去建立链接对象,这样每个子进程就有自己独立的mysql链接对象,因为子进程有自己独立的内存空间。经过多次测试,发现数据正常。

于是得出解决方法:父进程在自己系统查询到所有群列表后,立即关闭mysql连接,fork子进程后,子进程中就不会有mysql连接对象,经多次测试,数据正常,问题得以解决。

关闭mysql链接对象:在while循环前加上如下代码

 DB::disconnect('mysql');  

文章来源:智云一二三科技

文章标题:记一次PHP在多进程下批量插入mysql的异常

文章地址:https://www.zhihuclub.com/80004.shtml

关于作者: 智云科技

热门文章

网站地图