如何正确地处理 Laravel 中失败的队列任务?

Laravel

我想谈谈一些在这以外我通常看不到的内容,但是我个人认为这是一个健康的应用程序的关键:处理失败的作业

我将假设此时您已经了解了后台队列的工作方式。 如果不是这样,这里 可能是一个好的开始。

当您还有 8373653 件事要做,还有票要做的时候,处理失败的工作是很容易把视线移开的那类事情。您甚至可能认为关心这一点甚至不是你的工作(很可能不是这样)。但是 --IMO-- 我认为团队中的所有开发人员都应该时刻意识到并思考这一点。

因此,在本文中,我将介绍有关如何处理失败的作业的一些提示和技巧,不仅是在失败之后,而且还会在它们失败时。这篇文章将展示一些使用 Laravel 队列系统的示例,但是主要概念应该容易应用于任何语言/框架。

任何可能出错的地方都会出错

您知道 墨菲定律,对吗?所以,您已经知道事情将会失败,因此请尽可能多地拥抱它。您的代码可以完美运行数月,然后在那么美好的一天,您必须与之整合的糟糕 API 在没有任何事先通知的情况下发生了更改,或者 AWS 出现了问题或 [在此处插入随机失败原因]。

事实是,出乎意料的事情将会发生。因此处理您知道如何处理的事情,并确保当您不知道如何处理时会「更好地失败」。更好地失败的关键之一是拥有足够的信息来调试事实之后发生的事情。通常,日志记录是完成此类任务的最佳工具。

让我们看一个简单的代码示例,以更好地说明这一点:

<?php 

class UploadImageJob implements ShouldQueue
{
    public function __construct($path)
    {
        $this->path = $path;
    }

    public function handle(UploaderClient $client)
    {
          try {
            $client->upload($this->path);
          } catch (RateLimitException $exception) {
            $this->release($exception->getRetryAfter());
          } catch (Exception $exception) {
            Log::critical('[UploadImage] Unkown error when trying to upload image', [
                'error' => $exception->getMessage(),
                'path' => $this->path,
            ]);

            $this->fail($exception);
          }
    }
}

在上面的示例中,我们通过在速率限制到期后将作业释放回队列中,特别了解了如何处理 API 中的速率限制异常。我们还捕获了可能发生的一般错误,并应用了一些良好的日志记录信息,以便我们可以调试以后发生的情况。

重试的几率更高

在上面的示例中,作业可能失败的许多原因之一是因为我们正在使用的图像上传服务中断了。因此,如果您的工作对时间不敏感,那么实施某种指数回退策略可能是个好主意。

在 Laravel 中,可以使用尝试次数和 retryAfter 方法来完成此操作。

<?php 

public function retryAfter()
{
    return now()->addMinutes($this->attempts() * 5);
}

在这里,重要的是要注意队列工作者和(或)工作的最大尝试次数。另一种方法是直接调用 release 方法。

<?php 

class UploadImageJob implements ShouldQueue
{
    public function __construct($path)
    {
        $this->path = $path;
    }

    public function handle(UploaderClient $client)
    {
          try {
            $client->upload($this->path);
          } catch (Exception $exception) {
            $this->release($this->attempts() * 5);
          }
    }
}

这里必须注意的是,此方法可能并不是适用于所有作业的正确方法。因此请注意这一点。

保持您的 failed_jobs 表干净

Cloudfare 出现停机,基本上影响了整个Internet,并且您的大多数作业无法处理。确实,在停机期间您可以做的不多,但是您可以(也应该)始终回来重新运行失败的排队作业。它们全部(或至少应该)存储在失败的作业表中。

如果您一次不清理,那么下次可能就不会清理,因为该表中有一些旧记录,您不能完全确定它们会造成的影响。依此类推,直到表中有 5 万个失败的作业,然后别无选择,除非删除所有内容或假装不记得该表。

因此,请保持您失败的工作清洁。为此,我有一些技巧可以避免在保持桌子清洁的同时出现问题。

有些事情您不需要或无法重新运行

在某些情况下,重新运行一些失败的工作可能弊大于利。因此,重试失败的作业时请注意这一点。

例如,您不希望重新运行处理已在下一次命令或其他操作中处理过的事务的作业。或 3 个月后发送 「您的订单已送达」通知并没有多大意义。

Laravel 实际上没有批量重试或刷新特定作业的好方法,因此我写了 此程序包 添加了此功能从根本上讲,对您可能会有帮助。

因此,我的建议是删除不需要的内容,然后重新运行所需的内容。这使我讲到下一个要点。

尽可能使您的工作幂等

幂等,花哨的词。但基本上,您想确保如果同一作业多次运行,例如,它将不会多次向您的客户收费。

Another situation where this thinking is important is when updating a database record via a job payload. I work on this system recently that receives thousands of payloads to create/update/delete documents. Sometimes, for a number of reasons, these jobs fail. But if I re-run the jobs one day later, a new update for the document could have been executed already, with newer data. So in that case, I implemented a logic like this:
这种想法很重要的另一种情况是通过作业有效负载更新数据库记录时。我最近在这个系统上工作,该系统接收成千上万的有效载荷来创建/更新/删除文档。有时,由于多种原因,这些作业会失败。但是,如果一天后我重新运行作业,则该文档的新更新可能已经执行过,带有更新的数据。因此,在这种情况下,我实现了这样的逻辑:

<?php 

class UpdateDocumentJob implements ShouldQueue
{
    public function __construct($document, $payload)
    {
        $this->document = $document;
        $this-> payload = $payload;
    }

    public function handle()
    {
          if ($this->document->updated_at > $this->payload->updated_at) {
            Log::info("[UpdateDocumentJob] Not updating document because document last updated is greater than the payload last updated", [
                'document' => $this->document->id,
                'document_updated_at' => $this->document-> updated_at,
                'payload_updated_at' => $this->payload->updated_at,
            ]);

            return;
        }
    }
}

忽略缺失的模型

Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model

I bet you had a few of these in your Laravel life, right? In queued jobs, this can happen because Laravel serializes and unserializes your models when sending/retrieving the jobs to/from the queue. That's what that SerializesModels trait does.
我敢打赌,您在 Laravel 的生活中有其中一些吧?在排队的作业中,可能会发生这种情况,因为在向队列发送作业或从队列检索作业时,Laravel 会序列化和反序列化模型。这就是 SerializesModels 特性的作用。

<?php 

class SendArticleUpdatedWebhookJob implements ShouldQueue
{
    use SerializesModels;

    public $deleteWhenMissingModels = true;

    public function __construct(Article $article)
    {
        $this->article = $article;
    }
}

如果 Laravel 尝试反序列化您的模型(在幕后,它基本上会尝试再次使用 findOrFail从数据库中获取模型)并且删除了模型,则会引发 ModelNotFoundException。如果您不在乎您的工作,可以简单地在工作中设置 deleteWhenMissingModels 属性,Laravel将 [忽略丢失的模型](《Laravel 中文文档》 7.x / queues#ignoring-missing-models),它不会将作业发送到failed_jobs 表。

这可能是一个极端的情况,但是根据您要处理的作业数量的多少,这确实有助于保持环境清洁。

日志记录

排队作业以异步方式运行,并且您必须了解正在发生的事情(甚至成功)的最佳工具之一是日志记录。当您在某个地方确切地知道日志内容时,有时这听起来有些愚蠢,但我无法告诉您被这个问题咬了多少次。

您的本地数据和测试数据不是生产数据。请记住「可能出错的,就一定会出错」的部分?有时,您记录的一件简单的事情实际上可以节省您的调试时间。

我发现非常有用的一件事是使用所有不同的可用 日志级别 并设置一些基于级别的良好警报系统。

最后一件事,是与您的整个堆栈更为相关的,它是一个集中存放日志的地方,您可以在其中搜索和设置警报。您可能已经注意到,我喜欢在日志条目前添加作业名称作为「命名空间」。这对于搜索日志以及创建单独的日志流和警报非常有帮助。

我所拥有的就是这些,希望您能一直坚持到这里,对您有一定的帮助。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://dev.to/luisdalmolin/dealing-with...

译文地址:https://learnku.com/laravel/t/49484

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1

太 菜, 完全不知道说了什么

1周前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!