我正在使用Laravel Jobs从SQS队列中读取消息(Laravel版本5.7)
在Laravel indications之后,我正在使用主管同时运行多个queue:work
进程。
一切顺利,直到出现与消息可用性相关的SQS错误为止
InvalidParameterValue (client): Value
... for parameter ReceiptHandle is invalid. Reason: Message does not exist or
is not available for visibility timeout change. - <?xml version="1.0"?>
<ErrorResponse xmlns="http://queue.amazonaws.com/doc/2012-11-05/"><Error>
<Type>Sender</Type><Code>InvalidParameterValue</Code><Message>Value ...
for parameter ReceiptHandle is invalid. Reason: Message does not exist or is
not available for visibility timeout change.</Message><Detail/></Error>
<RequestId>8c1d28b7-a02c-5059-8b65-7c6292a0e56e</RequestId></ErrorResponse>
{"exception":"[object] (Aws\\Sqs\\Exception\\SqsException(code: 0): Error
executing \"ChangeMessageVisibility\" on \"https://sqs.eu-central-
1.amazonaws.com/123123123123/myQueue\"; AWS HTTP error: Client error: `POST
https://sqs.eu-central-1.amazonaws.com/123123123123/myQueue` resulted in a
`400 Bad Request` response:
尤其是奇怪的是Message does not exist or is not available for visibility timeout change.
每个主管进程在没有command=php /home/application/artisan queue:work
的情况下调用--sleep=3
(我希望该进程是响应性的,如果队列中没有任何内容,则不等待3秒)或--tries=3
(我需要完成所有任务,所以我没有限制tries
参数)
如果消息不存在(并且我不能排除这种可能性),为什么进程从队列中提取它?有什么我可以防止的吗?
答案 0 :(得分:0)
我在生产中也间歇性地看到了这个错误,我们为单个SQS队列运行了大量的使用者。在我们的案例中,我非常确信该错误是由于SQS的at-least-once传递语义引起的。本质上,在极少数情况下,邮件可以传递两次或多次。
Laravel的队列工作程序命令不是严格等幂的,因为它在尝试释放或删除不再可用的SQS消息时会抛出异常(即,由于它已被另一个队列工作程序进程删除,该进程收到重复的消息)来自SQS的消息。
我们的解决方法是尝试检测何时收到重复的邮件,然后尝试将邮件安全地释放回队列。如果当前正在处理该消息的另一个队列工作器成功,它将删除该消息,并且不会再次收到该消息。如果另一个队列工作器失败,则该消息将被释放并稍后再次接收。像这样:
<?php
use Aws\Sqs\Exception\SqsException;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $jobId;
public function __construct($jobId)
{
$this->jobId = $jobId;
}
public function handle()
{
$acquired = Cache::lock("process-podcast-$this->jobId")->get(function () {
// Process the podcast (NB: this should be idempotent)
});
if (!$acquired) {
$this->releaseDuplicateMessage($delay = 60);
}
}
private function releaseDuplicateMessage($delay)
{
try {
$this->release($delay);
} catch (Exception $ex) {
if (!$this->causedByMessageNoLongerAvailable($ex)) {
throw $ex;
}
}
}
private function causedByMessageNoLongerAvailable(Exception $ex): bool
{
return $ex instanceof SqsException &&
Str::contains(
$ex->getAwsErrorMessage(),
"Message does not exist or is not available for visibility timeout change"
);
}
}