Laravel queue:listen 超时时获取当前正在运行的任务

Get the current task being run when Laravel queue:listen times out

我们正在使用 supervisord / SQS 运行 Laravel 4,我们有 30 多个不同的任务正在使用 10 个工作进程运行。一切进展顺利,但似乎某些任务已开始超时。我们得到这样的异常:

[Symfony\Component\Process\Exception\ProcessTimedOutException]
The process ""/usr/bin/php5" artisan queue:work  --queue="https://sqs.us-east-     1.amazonaws.com/xxxx" --delay=0 --memory=128 --sleep=3 --tries=0 --env=development" exceeded the timeout of 180 seconds.

我可以用这个捕获这个异常:

App::error(function(Symfony\Component\Process\Exception\ProcessTimedOutException $exception) {

    /// caught!
});

但是我似乎无法确定正在运行哪个任务(发生超时时),如果我可以访问传递给任务的数据就更好了。

我已经尝试记录异常对象堆栈跟踪:

$exception->getTraceAsString()

但是,这并没有让我获得有关所调用任务的足够详细信息。

更新

我对 php artisan queue:listen 的工作原理做了更多研究。一些参考资料:

基本上,当您调用 php artisan queue:listen 时,会创建一个子进程(使用 Symfony/Component/Process),它实际上运行命令 php artisan queue:work。该子进程从队列中获取下一个作业,运行它,完成时报告,然后监听器产生另一个子进程来处理下一个作业。

因此,如果其中一个子进程花费的时间超过了已建立的超时限制,则 PARENT Listener 会抛出异常,但是,父实例没有关于它创建的子进程的数据。有轻微的例外!看起来父监听器确实处理子进程的输出。在我看来,父进程只不过是将子进程(工作者)的输出呈现到控制台。但是,也许有一种方法可以捕获此输出,以便在抛出异常时记录输出,从而了解发生超时时哪个任务正在运行!

我还注意到,在使用 supervisord 时,我们可以指定一个 stdout_logfile 来记录所有工作人员的输出。现在我们正在为我们所有的 10 个 supervisord "programs" 使用一个日志文件。我们可以将其更改为让每个 "program" 使用它自己的日志文件,然后也许当父监听器抛出超时异常时,我们可以让它获取该日志文件的最后 10 行。这也将为我们提供有关超时期间正在运行哪些任务的信息。但是,我不确定如何 "inform" 父 Listener 正在运行哪个 supervisord 程序,以便它知道要查看哪个日志文件!

查看异常 class (Symfony\Component\Process\Exception\ProcessTimedOutException) 我发现方法 getProcess() 其中 returns 是 Symfony\Component\Process\Process 的一个实例。那里有 getOutput()。该方法如其名称所述。

正如您在评论中所建议的那样,您可以通过在每个任务中回显 class 名称和参数来使用它,然后使用生成的输出来确定有问题的任务。正如你所说,它不是很优雅,但我想不出更好的方法(除了可能修补 Illuminate\Queue\Listener class...)

这里有一个你可以如何做到的例子(虽然未经测试)

我为输出选择了这种格式:

ClassName:ParametersAsJson;ClassName:ParametersAsJson;

所以在 BaseTask 中我会这样做:

abstract class BaseTask {
    public function fire(){
        echo get_class($this) . ':' . json_encode(func_get_args()) . ';';
    }
}

不幸的是,这意味着在每个任务中你都必须调用 parent::fire

class Task extends BaseTask {
    public function fire($param1, $param2){
        parent::fire($param1, $param2);

        // do stuff
    }
}

最后,异常处理程序:

App::error(function(Symfony\Component\Process\Exception\ProcessTimedOutException $exception) {

    $output = $exception->getProcess()->getOutput();
    $tasks = explode(';', $output);
    array_pop($output); // remove empty task that's here because of the closing ";"
    $lastTask = end($tasks);
    $parts = explode(':', $lastTask);

    $className = $parts[0];
    $parameters = json_decode($parts[1]);

    // write details to log

});

自 Laravel 4.1 以来,有一个用于处理失败作业的内置机制,其中所有作业详细信息都保存在数据库中或在异常时可用(或两者兼有)。 Laravel's website.

上提供了详细而清晰的文档

总结一下:

  1. Laravel 可以将失败的作业移至 failed_jobs table 以供以后查看
  2. 您可以通过Queue::failing注册一个异常处理程序,它将收到详细的作业信息以便立即处理

然而,在Laravel中超时是否被视为失败是值得怀疑的,所以这需要测试,因为我没有队列的实践经验。

如果您使用 Laravel 4.0,也许值得评估至少升级到 4.1,而不是编写复杂的代码,一旦您真的必须升级,这些代码就会变得多余(您 在某个时候升级,对吧?:))。 Upgrade path 看起来很简单。

虽然这不能直接回答您 Laravel 4.0 的问题,但您和任何未来 reader 都可以考虑。