Laravel retryUntil 作业在第 4 次重试没有失败后退出

Laravel retryUntil job exits after 4th retry without failing

我正在尝试调试我的 PHP 应用程序的一些奇怪行为。它是 运行 Laravel 6 + AWS SQS。该程序使用作业从 VoIP 提供商的 API 下载通话录音。 API 有 10req/minute 的严格速率限制,所以我正在限制我这边的请求。该作业配置为尝试使用 retryUntil 方法在 24 小时内完成。但是,该作业在尝试 4 次后从队列中消失。它不会失败。作业的 failed 方法永远不会执行(我已经将日志记录和 Sentry::capture 放在那里)。它不在 failed_jobs table 上。最后一条日志显示“无法完成作业,将在 ... 秒后重试”,它就在 release 调用之前。但是,作业只是从队列中消失,再也不会执行。

我正在记录尝试次数、最大尝试次数、超时时间等。一切似乎都已正确配置。这是我的代码的(本质):

    public function handle() 
    {
        /** @var Track $track */
        $track = Track::idOrUuId($this->trackId);

        $this->logger->info('Downloading track', [
            'trackId' => $track->getId(),
            'attempt' => $this->attempts(),
            'retryUntil' => $this->job->timeoutAt(),
            'maxTries' => $this->job->maxTries(),
        ]);

        $throttleKey = sprintf('track.download.%s', $track->getUser()->getTeamId());

        if (!$this->rateLimiter->tooManyAttempts($throttleKey, self::MAX_ALLOWED_JOBS)) {
            $this->downloadTrack($track);

            $this->rateLimiter->hit($throttleKey, 60);
        } else  {
            $delay = random_int(10, 100) + $this->rateLimiter->availableIn($throttleKey);

            $this->logger->info('Throttling track download.', [
                'trackId' => $track->getId(),
                'delay' => $delay,
            ]);

            $this->release($delay);
        }
    }

    public function retryUntil(): DateTimeInterface
    {
        return now()->addHours(24);
    }

    public function failed(Exception $exception)
    {
        $this->logger->info('Job failed', ['exception' => $exception->getMessage()];
        Sentry::captureException($exception);
    }

我发现了问题,并将其发布在这里,以供将来可能遇到困难的任何人使用。这一切都归结为一个简单的配置。在 AWS SQS 中,我正在使用的队列配置了 DLQ(Dead-Letter 队列)并且 Maximum receives 设置为 4。根据 SQS 文档

The Maximum receives value determines when a message will be sent to the DLQ. If the ReceiveCount for a message exceeds the maximum receive count for the queue, Amazon SQS moves the message to the associated DLQ (with its original message ID).

由于这是一个基础配置,它会覆盖您可能传递给作业的任何 Laravel 参数。并且因为消息只是简单地从队列中移除,处理作业实际上并没有失败,所以failed方法没有被执行。