先说一下遇到的问题,使用的是beanstalk队列,有两个tube, 使用 supervisor 监控 beanstalk 消费队列(主进程A),主进程A产生两个子进程(子进程B,子进程C),每个子进程处理一个tube的数据。

supervisor配置如下:

[program:queue-worker]
command=/usr/local/bin/php /var/www/html/ctc/console.php queue worker
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
startretries=30
startsecs=10

处理消费队列的代码如下:

/**
 * 启动消费队列
 *
 * @command php console.php queue worker
 */
public function wokerAction()
{
    echo "------ worker start ------" . PHP_EOL;

    $beanstalk = $this->getBeanstalk();
    $logger = $this->getLogger('queue');

    $beanstalk->addWorker(
        'main',
        function (BeanstalkJob $job) use ($config, $logger) {
            $taskId = $job->getBody();
            try {
                $manager = new MainQueue();
                $manager->handle($taskId);
            } catch (\Throwable $e) {
                $logger->error("tube:main, task:{$taskId} exception " . kg_json_encode([
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'message' => $e->getMessage(),
                    ]));
            }
            exit(0);
        }
    );

    $beanstalk->addWorker(
        'notice',
        function (BeanstalkJob $job) use ($config, $logger) {
            $taskId = $job->getBody();
            try {
                $manager = new NoticeQueue();
                $manager->handle($taskId);
            } catch (\Throwable $e) {
                $logger->error("tube:notice, task:{$taskId} exception " . kg_json_encode([
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'message' => $e->getMessage(),
                    ]));
            }
            exit(0);
        }
    );

    $beanstalk->doWork();
}

经常会出现下面的报错,子进程B或者C就退出了,但是主进程没事。

shmop_open(): unable to attach or create shared memory segment 'No such file or directory'

也查阅了supervisor的文档,里面有个针对组的配置,但是试了不起作用,这里的组配置应该是针对主进程的,主进程没事,子进程死活就不管了。

stopasgroup=true
killasgroup=true

这个事情折腾了好几天,子进程不定时的会死,一直找不到出现共享内存错误的原因,进程的创建使用的是现成的类库,如果总找不到原因程序就不稳定了。

后来转变思路,反正只有两个tube,干脆搞两个独立进程算了,不纠结什么子进程了,简单的事情搞复杂了,把监控的事情托付给 supervisor,让它干擅长的事情。此次事件不是 supervisor 的问题,是我们使用的姿势不对。

supervisor 配置如下:

[program:queue-main-worker]
command=/usr/local/bin/php /var/www/html/ctc/console.php queue main_worker
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
startretries=30
startsecs=10

[program:queue-notice-worker]
command=/usr/local/bin/php /var/www/html/ctc/console.php queue notice_worker
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
startretries=30
startsecs=10

处理消费队列的代码如下:

(1)处理 main tube 队列

    /**
     * 启动main消费队列
     *
     * @command php console.php queue main_worker
     */
    public function mainWorkerAction()
    {
        $tube = 'main';

        echo "------{$tube} worker start ------" . PHP_EOL;

        $beanstalk = $this->getBeanstalk();
        $logger = $this->getLogger('queue');

        while (true) {
            $job = $beanstalk->reserveFromTube($tube);
            if ($job instanceof BeanstalkJob) {
                $taskId = $job->getBody();
                try {
                    $manager = new MainQueue();
                    $manager->handle($taskId);
                    $job->delete();
                } catch (\Throwable $e) {
                    $logger->error("tube:{$tube}, task:{$taskId} exception " . kg_json_encode([
                            'file' => $e->getFile(),
                            'line' => $e->getLine(),
                            'message' => $e->getMessage(),
                        ]));
                }
            } else {
                sleep(1);
            }
        }
    }

(2)处理 notice tube 队列

    /**
     * 启动notice消费队列
     *
     * @command php console.php queue notice_worker
     */
    public function noticeWorkerAction()
    {
        $tube = 'notice';

        echo "------{$tube} worker start ------" . PHP_EOL;

        $beanstalk = $this->getBeanstalk();
        $logger = $this->getLogger('queue');

        while (true) {
            $job = $beanstalk->reserveFromTube($tube);
            if ($job instanceof BeanstalkJob) {
                $taskId = $job->getBody();
                try {
                    $manager = new NoticeQueue();
                    $manager->handle($taskId);
                    $job->delete();
                } catch (\Throwable $e) {
                    $logger->error("tube:{$tube}, task:{$taskId} exception " . kg_json_encode([
                            'file' => $e->getFile(),
                            'line' => $e->getLine(),
                            'message' => $e->getMessage(),
                        ]));
                }
            } else {
                sleep(1);
            }
        }
    }

经验总结

至于当初会选择使用fork子进程的方式,说到底还是图方便,因为有现成的东西,拿来就用,但是出了问题,又迟迟搞不定。吃过亏才发现,还是用简单稳定的方案实在,哪怕看上去没有那么美。

酷瓜云课堂-开源知识付费解决方案

项目组件

项目文档

意见反馈


鸠摩智首席音效师
461 声望4 粉丝

身强体健,龙精虎猛的活着。