首页 > 解决方案 > 如何在使用 Symfony Process 组件的请求期间异步启动 Symfony 命令?

问题描述

我正在尝试使用 Symfony Process 组件执行 Symfony 命令,以便在获取 API 请求时异步执行。

当我这样做时,我会收到错误消息Code: 127(Command not found),但是当我从控制台手动运行它时,它就像一个魅力。

这是电话:

public function asyncTriggerExportWitnesses(): bool
{
    $process = new Process(['php /var/www/bin/console app:excel:witness']);
    $process->setTimeout(600);
    $process->setOptions(['create_new_console' => true]);
    $process->start();
    $this->logInfo('pid witness export: ' . $process->getPid());
    if (!$process->isSuccessful()) {
        $this->logError('async witness export failed: ' . $process->getErrorOutput());
        throw new ProcessFailedException($process);
    }

    return true;
}

这是我得到的错误:

The command \"'php /var/www/bin/console app:excel:witness'\" failed.
Exit Code: 127(Command not found)
Working directory: /var/www/public
Output:================
Error Output:================
sh: exec: line 1: php /var/www/bin/console app:excel:witness: not found

我对 Process 组件的使用有什么问题?

像这样调用它也不起作用:

$process = new Process(['/usr/local/bin/php', '/var/www/bin/console', 'app:excel:witness']);

这会导致以下错误:

The command \"'/usr/local/bin/php' '/var/www/bin/console' 'app:excel:witness'\" failed.
Exit Code: ()
Working directory: /var/www/public
Output:
================
Error Output:
================

标签: phpsymfonysymfony5symfony-process

解决方案


首先,请注意 Process 组件并不意味着在父进程死亡后异步运行。因此,在 API 请求期间触发异步作业运行并不是一个好的用例。

文档中关于异步运行的这两条评论非常相关:

如果在子进程有机会完成之前发送了响应,则服务器进程将被终止(取决于您的操作系统)。这意味着您的任务将立即停止。运行异步进程与运行在其父进程中幸存的进程不同。

如果您希望您的进程在请求/响应周期中存活,您可以利用 kernel.terminate 事件,并在此事件中同步运行您的命令。请注意,仅当您使用 PHP-FPM 时才会调用 kernel.terminate。

另请注意,如果您这样做,则在子进程完成之前,上述 PHP-FPM 进程将无法为任何新请求提供服务。这意味着如果您不够小心,您可以快速阻止您的 FPM 池。这就是为什么即使在发送请求之后最好不要做任何花哨的事情,而是使用作业队列。

如果您想异步运行作业,只需将作业存储在“某处”(编辑数据库、redis、文本文件等),并让解耦的消费者通过“待处理的作业”并执行您需要的任何内容,而不会触发其中的作业API 请求。

上面的这个很容易实现,但你也可以使用 Symfony Messenger 来为你做这件事。根据您的 API 请求调度消息,使用您的作业队列消费者使用消息。


话虽如此,您对进程的使用也失败了,因为您正在尝试混合同步和异步方法。

您调用该命令的第二次尝试至少成功地找到了可执行文件,但因为您isSuccessful()在作业完成之前调用。

如果您使用start()( 而不是run()),则不能简单地isSuccessful()直接调用,因为工作尚未完成。

以下是执行异步作业的方式(尽管这在 API 请求期间很少有用):

class ProcessCommand extends Command
{

    protected static $defaultName = 'process_bg';

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $phpBinaryFinder = new PhpExecutableFinder();

        $pr = new Process([$phpBinaryFinder->find(), 'bin/console', 'bg']);
        $pr->setWorkingDirectory(__DIR__ . '/../..');

        $pr->start();

        while ($pr->isRunning()) {
            $output->write('.');
        }
        $output->writeln('');

        if ( ! $pr->isSuccessful()) {
            $output->writeln('Error!!!');

            return self::FAILURE;
        }

        $output->writeln('Job finished');

        return self::SUCCESS;
    }

}

推荐阅读