在Amazon SQS中从DLQ移除消息的最佳方式?

时间:2014-08-20 15:03:17

标签: amazon-web-services amazon-sqs

将邮件从死信队列移回Amazon SQS中的原始队列是什么最佳做法?

是不是

  1. 从DLQ获取消息
  2. 将消息写入队列
  3. 从DLQ删除邮件
  4. 或者有更简单的方法吗?

    此外,AWS最终会在控制台中使用一个工具来移除DLQ上的消息吗?

10 个答案:

答案 0 :(得分:73)

这是一个快速的黑客攻击。这绝对不是最好或推荐的选择。

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

答案 1 :(得分:9)

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

以下是我们实施的解决方案 -

通常,我们将DLQ用于瞬态错误,而不是永久性错误。所以采取了以下方法 -

  1. 从DLQ中读取消息,如常规队列

    优点
    • 避免重复邮件处理
    • 更好地控制DLQ-就像我进行检查一样,只有在完全处理常规队列时才进行处理。
    • 根据DLQ上的消息扩大流程
  2. 然后按照常规队列所遵循的相同代码进行操作。

  3. 在中止作业或处理过程中终止进程(例如实例已终止或进程终止)时更可靠

    优点
    • 代码可重用性
    • 错误处理
    • 恢复和留言重播
  4. 扩展消息可见性,以便其他线程不会处理它们。

    • 避免多个线程处理相同的记录。
  5. 仅在出现永久性错误或成功时删除消息。

    • 继续处理,直到我们遇到暂时性错误。

答案 2 :(得分:7)

这看起来是你最好的选择。在第2步之后,您的流程可能会失败。在这种情况下,您最终会复制邮件两次,但应用程序应该处理邮件的重新传递(或者不关心)。

答案 3 :(得分: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()

答案 4 :(得分:2)

我使用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

您可以在此link

中获得此脚本

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

答案 5 :(得分:2)

我们使用以下脚本将消息从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()

答案 6 :(得分:1)

如果不编写单行代码,还有另一种方法可以实现这一目标。 考虑您的实际队列名称是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队列。

答案 7 :(得分:0)

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

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

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

答案 8 :(得分:0)

有一些脚本可以为您做到这一点:

答案 9 :(得分:0)

AWS Lambda 解决方案对我们来说效果很好 -

详细说明: https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:303769779339:applications~aws-sqs-dlq-redriver

Github:https://github.com/honglu/aws-sqs-dlq-redriver

单击部署,再单击一次即可开始重新驱动!