将消息从死信队列移回Amazon SQS中的原始队列的最佳实践是什么?
可不可能是
- 从DLQ获取消息
- 写消息到队列
- 从DLQ删除消息
还是有更简单的方法?
而且,AWS最终会在控制台中提供一个工具来将消息移出DLQ吗?
将消息从死信队列移回Amazon SQS中的原始队列的最佳实践是什么?
可不可能是
还是有更简单的方法?
而且,AWS最终会在控制台中提供一个工具来将消息移出DLQ吗?
Answers:
这是一个快速的技巧。这绝对不是最佳或推荐的选择。
有一些脚本可以为您执行此操作:
# 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]
npx replay-aws-dlq DL_URI MAIN_URI
不需要移动消息,因为它将带来许多其他挑战,例如重复消息,恢复方案,丢失的消息,重复数据删除检查等。
这是我们实施的解决方案-
通常,我们将DLQ用于暂时性错误,而不是永久性错误。因此采取了以下方法-
像常规队列一样从DLQ中读取消息
好处然后遵循常规队列遵循的相同代码。
在中止作业或进程在处理过程中终止的情况下更为可靠(例如,实例被杀死或进程终止)
好处扩展消息的可见性,以便没有其他线程对其进行处理。
效益仅在出现永久错误或成功时删除消息。
效益这里:
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()
还有另一种无需编写任何代码即可实现此目的的方法。考虑您的实际队列名称为SQS_Queue,而其DLQ为SQS_DLQ。现在,请按照下列步骤操作:
aws sqs receive-message --queue-url <url of DLQ> --max-number-of-messages 10
。由于您可以阅读的最大邮件数上限为10,因此我建议像这样循环运行命令:for i in {1..1000}; do <CMD>; done
我使用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
字段。
我们使用以下脚本将消息从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()
仅当原始使用者在各种尝试后未能成功使用消息时,DLQ才起作用。我们不希望删除该消息,因为我们相信我们仍然可以对它进行处理(也许尝试再次处理或记录它或收集一些统计信息),并且我们不想一直反复遇到此消息并停止以下操作:处理此消息背后的其他消息。
DLQ只是另一个队列。这意味着我们需要为DLQ编写一个使用者,理想情况下,该使用者的运行频率(与原始队列相比)要低一些(与原始队列相比),它将从DLQ消耗并产生消息回到原始队列中并将其从DLQ中删除-如果这是预期的行为,我们认为原始消费者现在准备再次处理它。如果此周期持续一段时间,那应该没问题,因为我们现在也有机会手动检查并进行必要的更改,并部署原始消费者的另一个版本而不丢失消息(在消息保留期内,当然是4天)默认)。
如果AWS提供了开箱即用的功能,但我还没有看到,那就太好了-他们将其留给最终用户以他们认为合适的方式使用。