在Amazon SQS中从DLQ移出消息的最佳方法?


87

将消息从死信队列移回Amazon SQS中的原始队列的最佳实践是什么?

可不可能是

  1. 从DLQ获取消息
  2. 写消息到队列
  3. 从DLQ删除消息

还是有更简单的方法?

而且,AWS最终会在控制台中提供一个工具来将消息移出DLQ吗?



还有另一个替代方法github.com/mercury2269/sqsmover
Sergey

Answers:


131

这是一个快速的技巧。这绝对不是最佳或推荐的选择。

  1. 将主SQS队列设置为实际DLQ的DLQ,最大接收数为1。
  2. 查看DLQ中的内容(这会将消息移至主队列,因为这是实际DLQ的DLQ)
  3. 删除设置,以便主队列不再是实际DLQ的DLQ

12
是的,这很容易被破解-但如果您知道自己在做什么并且没有时间以正确的方式解决此问题,那么它是一种快速修复的不错选择#yolo
Thomas Watson

14
但是,当您执行此操作时,接收计数不会重置为0。小心。
Rajdeep Siddhapura

1
正确的方法是使用最大接收计数在SQS中配置“重新驱动策略”,当它超过设置的接收计数时,它将自动将消息移至DLQ,然后编写一个读取器线程以从DLQ进行读取。
阿什

5
你真是个天才
JefClaes

1
几个月前,我为这个问题创建了一个CLI工具:github.com/renanvieira/phoenix-letter
MaltMaster

14

有一些脚本可以为您执行此操作:

  • npm /基于nodejs:http ://github.com/garryyao/replay-aws-dlq
# install
npm install replay-aws-dlq;

# use
npx replay-aws-dlq [source_queue_url] [dest_queue_url]
# compile: https://github.com/mercury2269/sqsmover#compiling-from-source

# use
sqsmover -s [source_queue_url] -d [dest_queue_url] 

1
与公认的答案不同,这是最简单的方法。只需在设置了AWS env vars属性的终端上运行此命令:npx replay-aws-dlq DL_URI MAIN_URI
Vasyl Boroviak

注意输入错误:dql-> dlq#install npm install replay-aws-dlq;
Lee Oades

这对我来说是完美的(请注意,我只尝试过基于go的程序)。似乎分阶段而不是一次全部移动消息(一件好事),甚至还有一个进度条。比国际海事组织的答案更好。
Yevgeny Ananin

13

不需要移动消息,因为它将带来许多其他挑战,例如重复消息,恢复方案,丢失的消息,重复数据删除检查等。

这是我们实施的解决方案-

通常,我们将DLQ用于暂时性错误,而不是永久性错误。因此采取了以下方法-

  1. 像常规队列一样从DLQ中读取消息

    好处
    • 为了避免重复的消息处理
    • 更好地控制DLQ-就像我进行检查一样,仅在常规队列已完全处理时才进行处理。
    • 根据DLQ上的消息扩大流程
  2. 然后遵循常规队列遵循的相同代码。

  3. 在中止作业或进程在处理过程中终止的情况下更为可靠(例如,实例被杀死或进程终止)

    好处
    • 代码可重用性
    • 错误处理
    • 恢复和消息重播
  4. 扩展消息的可见性,以便没有其他线程对其进行处理。

    效益
    • 避免通过多个线程处理同一记录。
  5. 仅在出现永久错误或成功时删除消息。

    效益
    • 继续处理,直到遇到暂时性错误。

我真的很喜欢你的方法!在这种情况下,您如何定义“永久错误”?
DMac毁灭者

大于HTTP状态代码> 200 <500的任何内容都是永久性错误
Ash

这确实是生产中的好方法。但是我认为这篇文章只是问如何将消息从DLQ重新发布到普通队列。如果您知道自己在做什么,有时会很方便。
linehrr

那就是我在说你不应该这样做。因为如果这样做,则会造成更多问题。我们可以像其他任何消息推送一样移动消息,但是将丢失DLQ功能,例如接收计数,可见性和所有功能。它将被视为新消息。
Ash Ash

6

这似乎是您的最佳选择。在步骤2之后,您的过程可能会失败。在这种情况下,您最终将消息复制两次,但是您的应用程序无论如何都应该处理消息的重新传递。


6

这里:

import boto3
import sys
import Queue
import threading

work_queue = Queue.Queue()

sqs = boto3.resource('sqs')

from_q_name = sys.argv[1]
to_q_name = sys.argv[2]
print("From: " + from_q_name + " To: " + to_q_name)

from_q = sqs.get_queue_by_name(QueueName=from_q_name)
to_q = sqs.get_queue_by_name(QueueName=to_q_name)

def process_queue():
    while True:
        messages = work_queue.get()

        bodies = list()
        for i in range(0, len(messages)):
            bodies.append({'Id': str(i+1), 'MessageBody': messages[i].body})

        to_q.send_messages(Entries=bodies)

        for message in messages:
            print("Coppied " + str(message.body))
            message.delete()

for i in range(10):
     t = threading.Thread(target=process_queue)
     t.daemon = True
     t.start()

while True:
    messages = list()
    for message in from_q.receive_messages(
            MaxNumberOfMessages=10,
            VisibilityTimeout=123,
            WaitTimeSeconds=20):
        messages.append(message)
    work_queue.put(messages)

work_queue.join()

这是Python吗?
carlin.scott

python2实际上
Kristof Jozsa

4

还有另一种无需编写任何代码即可实现此目的的方法。考虑您的实际队列名称为SQS_Queue,而其DLQ为SQS_DLQ。现在,请按照下列步骤操作:

  1. 将SQS_Queue设置为SQS_DLQ的dlq。由于SQS_DLQ已经是SQS_Queue的dlq。现在,两者都充当对方的dlq。
  2. 将SQS_DLQ的最大接收计数设置为1。
  3. 现在从SQS_DLQ控制台读取消息。由于消息接收计数为1,它将把所有消息发送到自己的dlq,即您的实际SQS_Queue队列。

这将破坏维护DLQ的目的。DLQ的目的是在观察故障时不会使系统过载,以便以后可以执行此操作。
佛陀

它将肯定无法实现目标,您将无法获得其他好处,例如扩大规模,节流并获得计数。此外,您应该将常规队列用作处理队列,如果消息接收计数达到“ N”,则应转到DLQ。这是理想的配置。
阿什

3
作为重新发送大量消息的一种一次性解决方案,它的工作原理就像一种魅力。但是,这不是一个好的长期解决方案。
nmio

是的,这是一次性重新发送消息的解决方案(在解决了主队列中的问题之后)非常有价值。在AWS CLI上,我使用的命令是:aws sqs receive-message --queue-url <url of DLQ> --max-number-of-messages 10。由于您可以阅读的最大邮件数上限为10,因此我建议像这样循环运行命令:for i in {1..1000}; do <CMD>; done
Patrick Finnigan

3

我使用boto3 lib编写了一个小的python脚本来执行此操作:

conf = {
  "sqs-access-key": "",
  "sqs-secret-key": "",
  "reader-sqs-queue": "",
  "writer-sqs-queue": "",
  "message-group-id": ""
}

import boto3
client = boto3.client(
    'sqs',
        aws_access_key_id       = conf.get('sqs-access-key'),
        aws_secret_access_key   = conf.get('sqs-secret-key')
)

while True:
    messages = client.receive_message(QueueUrl=conf['reader-sqs-queue'], MaxNumberOfMessages=10, WaitTimeSeconds=10)

    if 'Messages' in messages:
        for m in messages['Messages']:
            print(m['Body'])
            ret = client.send_message( QueueUrl=conf['writer-sqs-queue'], MessageBody=m['Body'], MessageGroupId=conf['message-group-id'])
            print(ret)
            client.delete_message(QueueUrl=conf['reader-sqs-queue'], ReceiptHandle=m['ReceiptHandle'])
    else:
        print('Queue is currently empty or messages are invisible')
        break

您可以在此链接中获取此脚本

该脚本基本上可以在任意队列之间移动消息。它支持fifo队列,您也可以提供该message_group_id字段。


3

我们使用以下脚本将消息从src队列重新驱动到tgt队列:

文件名: redrive.py

用法: python redrive.py -s {source queue name} -t {target queue name}

'''
This script is used to redrive message in (src) queue to (tgt) queue

The solution is to set the Target Queue as the Source Queue's Dead Letter Queue.
Also set Source Queue's redrive policy, Maximum Receives to 1. 
Also set Source Queue's VisibilityTimeout to 5 seconds (a small period)
Then read data from the Source Queue.

Source Queue's Redrive Policy will copy the message to the Target Queue.
'''
import argparse
import json
import boto3
sqs = boto3.client('sqs')


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('-s', '--src', required=True,
                        help='Name of source SQS')
    parser.add_argument('-t', '--tgt', required=True,
                        help='Name of targeted SQS')

    args = parser.parse_args()
    return args


def verify_queue(queue_name):
    queue_url = sqs.get_queue_url(QueueName=queue_name)
    return True if queue_url.get('QueueUrl') else False


def get_queue_attribute(queue_url):
    queue_attributes = sqs.get_queue_attributes(
        QueueUrl=queue_url,
        AttributeNames=['All'])['Attributes']
    print(queue_attributes)

    return queue_attributes


def main():
    args = parse_args()
    for q in [args.src, args.tgt]:
        if not verify_queue(q):
            print(f"Cannot find {q} in AWS SQS")

    src_queue_url = sqs.get_queue_url(QueueName=args.src)['QueueUrl']

    target_queue_url = sqs.get_queue_url(QueueName=args.tgt)['QueueUrl']
    target_queue_attributes = get_queue_attribute(target_queue_url)

    # Set the Source Queue's Redrive policy
    redrive_policy = {
        'deadLetterTargetArn': target_queue_attributes['QueueArn'],
        'maxReceiveCount': '1'
    }
    sqs.set_queue_attributes(
        QueueUrl=src_queue_url,
        Attributes={
            'VisibilityTimeout': '5',
            'RedrivePolicy': json.dumps(redrive_policy)
        }
    )
    get_queue_attribute(src_queue_url)

    # read all messages
    num_received = 0
    while True:
        try:
            resp = sqs.receive_message(
                QueueUrl=src_queue_url,
                MaxNumberOfMessages=10,
                AttributeNames=['All'],
                WaitTimeSeconds=5)

            num_message = len(resp.get('Messages', []))
            if not num_message:
                break

            num_received += num_message
        except Exception:
            break
    print(f"Redrive {num_received} messages")

    # Reset the Source Queue's Redrive policy
    sqs.set_queue_attributes(
        QueueUrl=src_queue_url,
        Attributes={
            'VisibilityTimeout': '30',
            'RedrivePolicy': ''
        }
    )
    get_queue_attribute(src_queue_url)


if __name__ == "__main__":
    main()

0

仅当原始使用者在各种尝试后未能成功使用消息时,DLQ才起作用。我们不希望删除该消息,因为我们相信我们仍然可以对它进行处理(也许尝试再次处理或记录它或收集一些统计信息),并且我们不想一直反复遇到此消息并停止以下操作:处理此消息背后的其他消息。

DLQ只是另一个队列。这意味着我们需要为DLQ编写一个使用者,理想情况下,该使用者的运行频率(与原始队列相比)要低一些(与原始队列相比),它将从DLQ消耗并产生消息回到原始队列中并将其从DLQ中删除-如果这是预期的行为,我们认为原始消费者现在准备再次处理它。如果此周期持续一段时间,那应该没问题,因为我们现在也有机会手动检查并进行必要的更改,并部署原始消费者的另一个版本而不丢失消息(在消息保留期内,当然是4天)默认)。

如果AWS提供了开箱即用的功能,但我还没有看到,那就太好了-他们将其留给最终用户以他们认为合适的方式使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.