获取当前正在运行的任务,当 Laravel 队列:侦听超时时


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

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

[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的工作原理做了更多的研究。一些参考资料:

  • 亮起/队列/控制台/侦听
  • 亮起/
  • 队列/侦听
  • Symfony/Component/Process

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

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

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

查看异常类 ( Symfony'Component'Process'Exception'ProcessTimedOutException ) 我找到了返回 Symfony'Component'Process'Process 实例的方法getProcess()。在那里你得到了getOutput().该方法按照其名称所述进行操作。

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

这是一个如何做到这一点的示例(尽管未经测试)

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

ClassName:ParametersAsJson;ClassName:ParametersAsJson;

所以在基本任务中,我会这样做:

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的网站上找到。

总结一下:

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

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

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

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