【问题标题】:Queued Laravel Notifications get stuck on AWS SQS排队的 Laravel 通知卡在 AWS SQS 上
【发布时间】:2021-02-21 09:42:42
【问题描述】:

我在 AWS 上有一个工作人员来处理排队的 Laravel 通知。一些通知被发送出去,但另一些则被卡在队列中,我不知道为什么。

我查看了 Beanstalk 中的日志,发现了三种不同类型的错误:

2020/11/03 09:22:34 [emerg] 10932#0: *30 malloc(4096) failed (12: Cannot allocate memory) while reading upstream, client: 127.0.0.1, server: , request: "POST /worker/queue HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/www.sock:", host: "localhost"

我在 Bugsnag 上也看到了内存不足问题,但没有任何堆栈跟踪。

另一个错误是这个:

2020/11/02 14:50:07 [error] 10241#0: *2623 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: , request: "POST /worker/queue HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/www.sock", host: "localhost"

这是最后一个:

2020/11/02 15:00:24 [error] 10241#0: *2698 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 127.0.0.1, server: , request: "POST /worker/queue HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/www.sock:", host: "localhost"

我真的不明白我可以做些什么来解决这些错误。这只是一个基本的 Laravel / EBS / SQS 设置,队列唯一要做的就是处理通知。有时一次几十个。我正在运行t2.micro,并认为这足以发送几封电子邮件?我已将环境升级为t2.large,但无济于事。

我注意到消息最终进入队列,然后得到状态“消息在飞行中”,但随后在 Laravel 方面遇到了各种麻烦。但我没有遇到任何有用的错误。

所有实现代码似乎都很好,因为前几个通知按预期发出,如果我根本不排队,所有通知都会立即发送。

排队的通知最终会产生两个不同的异常:MaxAttemptsExceededExceptionOut of Memory FatalError,但都没有让我发现真正的潜在问题。

我在哪里可以进一步调试?


更新

请参阅我对问题和解决方案的回答。在工作人员尝试为仍需创建的对象发送通知之前,数据库事务尚未完成。

【问题讨论】:

  • 你在 /var/log/fpm-php.www.log 中有什么?
  • @mirza 我没有那个日志文件,但我有 /var/log/php-fpm/error.log 如果这就是你的意思。我看到fpm is running, pid 30428 -> ready to handle connections -> systemd monitor interval set to 10000ms -> Terminating ... -> exiting, bye-bye! 的循环
  • @jeremykenedy 我不认为 Supervisor 已配置,所以我可以看看。

标签: php laravel amazon-elastic-beanstalk amazon-sqs


【解决方案1】:

当前分配给 PHP 的 memory_limit 是多少?您可以通过运行以下命令来确定这一点:

php -i | grep memory_limit

您可以通过运行以下命令来增加此值:

sed -i -e 's/memory_limit = [current-limit]/memory_limit = [new-limit]/g' [full-path-to-php-ini]

只需将 [current-limit] 替换为第一个命令中显示的值,并将 [new-limit] 替换为新的合理值。这可能需要反复试验。将 [full-path-to-php-ini] 替换为失败的进程使用的 php.ini 的完整路径。要找到它,请运行:

php -i | grep php.ini

【讨论】:

  • 我添加了一个解释问题和解决方案的答案。感谢您的帮助!
【解决方案2】:

首先确保您增加了max_execution_timememory_limit
还要确保设置--timeout 选项
然后确保按照 laravel doc 中的 Amazon SQS 说明进行操作

唯一不包含 retry_after 值的队列连接是 Amazon SQS。 SQS 将根据在 AWS 控制台中管理的 Default Visibility Timeout 重试该作业。

Job Expirations & Timeouts

【讨论】:

  • 这似乎是解决方案!我们将在本周进行测试,然后我将奖励赏金。 :-)
  • 我们这样做了,它似乎有效,但现在我们又遇到了类似的问题。我们将值 van 30 提高到 100。它应该是 -1 吗?或者对于工作人员的执行时间还有什么合理的价值?
  • 嗯,这应该是您认为完成工作需要时间的最长时间。亚马逊最多只支持 12 小时
  • 问题是它应该只需要几秒钟。我现在所做的是将 Beanstalk Worker 配置中的连接数从 10 减少到 1,然后结果会好一些。当我预计有 200 封邮件时,我收到了 199 封邮件,只有第一封邮件丢失了。有什么想法吗?
  • 不幸的是,这就是我所知道的全部
【解决方案3】:

如果您确定某些排队的事件被 worker Laravel 正确接收和处理,那么正如其他人所说,这主要是 PHP 内存问题。

在 beanstalk 上,这是我添加到我的 ebextensions 以获得更大的 PHP 内存(它用于作曲家内存问题):

请注意,这是一个带有 4go 的 t3.medium EC2 实例,仅专用于 laravel API。

02-environment.config
commands:
   ...

option_settings:
  ...

  - namespace: aws:elasticbeanstalk:container:php:phpini
    option_name: memory_limit
    value: 4096M
    
  - namespace: aws:ec2:instances
    option_name: InstanceTypes
    value: t3.medium

所以你可以尝试增加使用更多可用实例最大内存的限制,然后再次部署,这样 beanstalk 将重建实例并设置 PHP memory_limit

注意:真正的配置当然包含其他配置文件和更多截断的内容。

正如你所说,你只是发送一封电子邮件,所以应该没问题。当有大量电子邮件排队时会发生这种情况吗?最后,SQS deadLetterQueue 中是否存在许多事件?如果是这样,可能是因为排队的电子邮件突发。所以 SQS 将“淹没” /worker 路由来执行你的工作。您可以从 AWS 控制台或 htop 之类的 CLI 工具中检查服务器使用情况以进行监控,还可以检查 SQS 界面以查看是否同时出现许多失败的作业(突发)。

编辑:对于弹性豆茎,我使用dusterio/laravel-aws-worker,也许你也是,因为你的日志提到了/worker/queue 路由

【讨论】:

  • 毕竟不是内存问题。 :-) 我添加了一个解释问题和解决方案的答案。感谢您的帮助!
【解决方案4】:

内存

分配给 PHP 的默认内存量通常非常小。使用 EBS 时,您希望尽可能多地使用配置文件 - 任何时候您必须 SSH 并更改服务器上的内容,当您需要重新部署时,您将遇到更多问题。我已将此添加到我的 EBS 配置 /.ebextensions/01-php-settings.config:

option_settings:
  aws:elasticbeanstalk:container:php:phpini:
    memory_limit: 256M

运行t3.micro 来完成我所有的通知和导入处理就足够了。对于简单的处理,它通常不需要比默认更多的内存,但这在很大程度上取决于您的用例以及您对通知的编程方式。

超时

正如this answer 中已经指出的那样,SQS 队列在超时方面的操作略有不同。这是我写的一个小特征来帮助解决这个问题:

<?php

namespace App\Jobs\Traits;

trait CanExtendSqsVisibilityTimeout
{
    /** NOTE: this needs to map to setting in AWS console */
    protected $defaultBackoff = 30;

    protected $backoff = 30;

    /**
     * Extend the time that the job is locked for processing
     *
     * SQS messages are managed via the default visibility timeout console setting; noted absence of retry_after config
     * @see https://laravel.com/docs/7.x/queues#job-expirations-and-timeouts
     * AWS recommends to create a "heartbeat" in the consumer process in order to extend processing time:
     * @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html#configuring-visibility-timeout
     *
     * @param int $delay  Number of seconds to extend the processing time by
     *
     * @return void
     */
    public function extendBackoff($delay = 60)
    {
        if ($this->job) {
            // VisibilityTimeout has a 12 hour (43200s) maximum and will error above that; no extensions if close to it
            if ($this->backoff + $delay > 42300) {
                return;
            }
            // add the delay
            $this->backoff += $delay;
            $sqs = $this->job->getSqs();
            $sqsJob = $this->job->getSqsJob();
            $sqs->changeMessageVisibility([
                'QueueUrl' => $this->job->getQueue(),
                'ReceiptHandle' => $sqsJob['ReceiptHandle'],
                'VisibilityTimeout' => $this->backoff,
            ]);
        }
    }
}

然后,对于需要很长时间的排队作业,我稍微更改了代码以找出可以插入合理“心跳”的位置。就我而言,我有一个循环:

class LongRunningJob implements ShouldQueue
{
    use CanExtendSqsVisibilityTimeout;

    //...

    public function handle()
    {
        // some other processing, no loops involved

        // now the code that loops!
        $last_extend_at = time();
        foreach ($tasks as $task) {
            $task->doingSomething();

            // make sure the processing doesn't time out, but don't extend time too often
            if ($last_extend_at + $this->defaultBackoff - 10 > time()) {
                // "heartbeat" to extend visibility timeout
                $this->extendBackoff();
                $last_extend_at = time();
            }
        }
}

主管

听起来您可能需要更详细地了解您是如何运行您的工作人员的。

我认为,让 Supervisor 运行以帮助重新启动您的工作人员是必须的。否则,如果工作人员停止工作,排队的消息将在过期时最终被删除。使用 Laravel + EBS 很好地工作有点繁琐——没有太多好的文档围绕它,这可能是为什么不必管理它是 Vapor 的卖点之一!

【讨论】:

  • 是的,我们会找一位主管。与此同时,我们已经解决了这个问题。我添加了一个解释问题和解决方案的答案。感谢您的帮助!
【解决方案5】:

我们终于找到了问题所在,不是内存或执行时间。

从一开始我就觉得很奇怪,默认内存或默认执行时间都不足以发送一两封电子邮件。

我们的用例是:创建一个新的Article,用户会收到通知。

导致解决方案的一些线索:

  • 我们注意到我们通常在 first 通知方面遇到问题。
  • 如果我们同时创建 10 篇文章,我们会错过每篇文章的第一个通知。
  • 我们将 Worker 中的 HTTP Max Connections 设置为 1。同时创建 10 篇文章时,我们注意到只有第一篇文章错过了第一个通知。
  • 我们没有从 Worker 收到任何有用的错误消息,因此我们决定设置自己的 EC2 并手动运行 php artisan queue

我们随后看到的解释了一切: Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Article]

这是我们从未从 EBS Worker / SQS 得到的错误,并迅速导致了解决方案:

在文章进入数据库之前处理通知。

我们为工作人员添加了延迟,从那时起就没有出现过问题。我们最近在创建文章的过程中添加了一个数据库事务,并在该事务中创建通知(但在最后)。我想这就是为什么我们以前没有这个问题。我们决定将通知创建留在事务中,并延迟处理通知。这意味着我们无需进行修补程序即可解决此问题。

感谢所有加入帮助的人!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-05
    • 1970-01-01
    • 2017-08-12
    • 2019-02-14
    • 1970-01-01
    • 2021-07-03
    • 2021-12-18
    • 1970-01-01
    相关资源
    最近更新 更多