Varobj

2020-10-28

phalcon 多进程报错问题



问题描述

开启多进程时,要关闭父进程已经打开的 MySQL 连接实例,否则就会报错(如:MySQL server has gone away),也就是说子进程必须重新建立相应的 socket 连接,不能复用父进程的。但是 socket 连接是比较耗时的操作,生命周期内如果需要多次连接 MySQL,那么使用单例模式是提高性能的通用做法,事实上大部分 web 应用的一次生命周期内,确实需要多次连接 MySQL 等。

Phalcon 中一般通过 DI 来注入 MySQL 等服务,DI 注入有两种方式,一种是 set ,一种是 setShared ,顾名思义 setShared 就相当于注入单例模式的服务,通过 get 或者 getShared 获取 setShared 的服务实例都是单例的。

基于以上两点,在需要开启多进程的地方,如果你的服务是 setShared 方式注入的(事实基于性能考虑,大多都是单例模式),必须手动处理释放单例的内存缓存。否则可能会报错。

如下:开启两个进程,父进程查询一次 db ,子进程查询一次 db ,错误不是必现的,开启进程越多报错的可能性越大。

[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21643][MP:1/2] 加锁成功
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21643][MP:1/2] 进程编号 1
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21644][MP:2/2] 加锁成功
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21644][MP:2/2] 进程编号 2
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21643][MP:1/2] config 表共 1 条记录
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21643][MP:1/2] ---------- 脚本结束 ----------
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21643][MP:1/2] 开锁成功
{
    "status": "error",
    "error": {
        "e_src": "exception",
        "e_no": "HY000",
        "e_msg": "SQLSTATE[HY000]: General error: 2006 MySQL server has gone away | debug: [\"SQLSTATE[HY000]: General error: 2006 MySQL server has gone away\"]",
        "e_pos": "\/vagrant_data\/NextJob\/backend\/app\/Task\/MainTask.php@24",
        "errno": "HY000",
        "errmsg": "系统异常",
        "ignore_log": false
    }
}
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21644][MP:2/2] ---------- 脚本结束 ----------
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21644][MP:2/2] 开锁成功
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21642][MP:0/2] child 21643 exit!
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21642][MP:0/2] child 21644 exit!
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21642][MP:0/2] ---> 0.182 sec <---
[DT:11/27/06][REQ:44C78F250B6DCB518147][PID:21642][MP:0/2] ---------- 脚本结束 ----------

解决方法

首先相到的方法,在开启子进程之前,关闭父进程中的 MySQL 连接


$this->di->getShared('db')->close();

// 开启多进程..

事实证明只关闭 MySQL 连接是无效的,因为 PhalconMySQL 相关的 close 方法是通过删除 pdo 对象的方式,但是 setShared 会缓存包含 MySQL 连接实例的一个单独的 service 类,close 只是删除缓存实例的对象名称,下次 getShared 的时候,还是会从缓存中获取第一次建立的 MySQL 连接实例,通过查看源码,可以通过以下方式

function reConnectionMySQL()
{
    if ($this->di->has('db') && $this->di->getService('db')->isShared()) {
        $db = $this->di->getService('db');
        $this->di->remove('db');
        $this->di->setShared('db', $db->getDefinition());
    }
    if ($this->di->has('db_read') && $this->di->getService('db_read')->isShared()) {
        $db = $this->di->getService('db_read');
        $this->di->remove('db_read');
        $this->di->setShared('db_read', $db->getDefinition());
    }
    if ($this->di->has('db_write') && $this->di->getService('db_write')->isShared()) {
        $db = $this->di->getService('db_write');
        $this->di->remove('db_write');
        $this->di->setShared('db_write', $db->getDefinition());
    }
}

由于我的代码 service.php 中设置了三种 MySQL 相关服务,db 用于单数据库,db_read & db_write 用于读写分离模式的数据库,所以判断了三次。首先要判断 MySQL 服务是否存在,并且是否是 shared 模式,如果不是也不需要处理,因为非 shared 模式每次查询数据库,都会重新建立连接,开启多进程也不会遇到此处的报错。然后在 DIremove 掉之前的服务,之后再重新 setShared 相关服务即可,虽然麻烦,但是可以彻底解决问题。最后只需要在开启多进程之前,调用 reConnectionMySQL 函数即可。

改进之后,随便怎么加子进程数量,也不会报之前的错误了

[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22726][MP:1/2] 加锁成功
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22726][MP:1/2] 进程编号 1
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22727][MP:2/2] 加锁成功
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22727][MP:2/2] 进程编号 2
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22727][MP:2/2] config 表共 1 条记录
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22727][MP:2/2] ---------- 脚本结束 ----------
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22727][MP:2/2] 开锁成功
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22726][MP:1/2] config 表共 1 条记录
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22726][MP:1/2] ---------- 脚本结束 ----------
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22726][MP:1/2] 开锁成功
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22725][MP:0/2] child 22727 exit!
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22725][MP:0/2] child 22726 exit!
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22725][MP:0/2] ---> 0.187 sec <---
[DT:14/13/54][REQ:64E33E8729893F480C26][PID:22725][MP:0/2] ---------- 脚本结束 ----------