[英]Best way to move messages off DLQ in Amazon SQS?
將消息從死信隊列移回 Amazon SQS 中的原始隊列的最佳做法是什么?
可不可能是
或者有更簡單的方法嗎?
此外,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]
不需要移動消息,因為它會帶來許多其他挑戰,如重復消息、恢復場景、丟失消息、重復數據刪除檢查等。
這是我們實施的解決方案 -
通常,我們將 DLQ 用於暫時性錯誤,而不是永久性錯誤。 所以采取了以下方法 -
像普通隊列一樣從 DLQ 讀取消息
好處然后遵循常規隊列所遵循的相同代碼。
在中止作業或進程在處理時終止的情況下更可靠(例如實例被終止或進程終止)
好處擴展消息可見性,以便沒有其他線程處理它們。
益處僅當出現永久性錯誤或成功時才刪除消息。
益處我使用 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
您可以在此鏈接中獲取此腳本
這個腳本基本上可以在任意隊列之間移動消息。 它支持先進先出隊列以及您可以提供message_group_id
字段。
這看起來是你最好的選擇。 您的流程有可能在第 2 步之后失敗。在這種情況下,您最終將復制消息兩次,但您的應用程序應該處理消息的重新傳遞(或不關心)。
這里:
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。 現在按照以下步驟操作:
我們使用以下腳本將消息從 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 提供這種開箱即用的功能會很好,但我還沒有看到它 - 他們將它留給最終用戶以他們認為合適的方式使用它。
這也是將消息從一個 AWS 隊列移動到另一個 AWS 隊列的腳本(用Typescript編寫)。 也許它對某人有用。
import {
SQSClient,
ReceiveMessageCommand,
DeleteMessageBatchCommand,
SendMessageBatchCommand,
} from '@aws-sdk/client-sqs'
const AWS_REGION = 'eu-west-1'
const AWS_ACCOUNT = '12345678901'
const DLQ = `https://sqs.${AWS_REGION}.amazonaws.com/${AWS_ACCOUNT}/dead-letter-queue`
const QUEUE = `https://sqs.${AWS_REGION}.amazonaws.com/${AWS_ACCOUNT}/queue`
const loadMessagesFromDLQ = async () => {
const client = new SQSClient({region: AWS_REGION})
const command = new ReceiveMessageCommand({
QueueUrl: DLQ,
MaxNumberOfMessages: 10,
VisibilityTimeout: 60,
})
const response = await client.send(command)
console.log('---------LOAD MESSAGES----------')
console.log(`Loaded: ${response.Messages?.length}`)
console.log(JSON.stringify(response, null, 4))
return response
}
const sendMessagesToQueue = async (entries: Array<{Id: string, MessageBody: string}>) => {
const client = new SQSClient({region: AWS_REGION})
const command = new SendMessageBatchCommand({
QueueUrl: QUEUE,
Entries: entries.map(entry => ({...entry, DelaySeconds: 10})),
// [
// {
// Id: '',
// MessageBody: '',
// DelaySeconds: 10
// }
// ]
})
const response = await client.send(command)
console.log('---------SEND MESSAGES----------')
console.log(`Send: Successful - ${response.Successful?.length}, Failed: ${response.Failed?.length}`)
console.log(JSON.stringify(response, null, 4))
}
const deleteMessagesFromQueue = async (entries: Array<{Id: string, ReceiptHandle: string}>) => {
const client = new SQSClient({region: AWS_REGION})
const command = new DeleteMessageBatchCommand({
QueueUrl: DLQ,
Entries: entries,
// [
// {
// "Id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
// "ReceiptHandle": "someReceiptHandle"
// }
// ]
})
const response = await client.send(command)
console.log('---------DELETE MESSAGES----------')
console.log(`Delete: Successful - ${response.Successful?.length}, Failed: ${response.Failed?.length}`)
console.log(JSON.stringify(response, null, 4))
}
const run = async () => {
const dlqMessageList = await loadMessagesFromDLQ()
if (!dlqMessageList || !dlqMessageList.Messages) {
console.log('There is no messages in DLQ')
return
}
const sendMsgList: any = dlqMessageList.Messages.map(msg => ({ Id: msg.MessageId, MessageBody: msg.Body}))
const deleteMsgList: any = dlqMessageList.Messages.map(msg => ({ Id: msg.MessageId, ReceiptHandle: msg.ReceiptHandle}))
await sendMessagesToQueue(sendMsgList)
await deleteMessagesFromQueue(deleteMsgList)
}
run()
PS 該腳本有改進的余地,但無論如何可能會有用。
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 。
一鍵部署,再單擊即可開始重新驅動!
AWS 終於發布了將消息從 DLQ 重新驅動回源隊列的功能。 剛剛自己測試了它,它的工作原理完全符合您的預期,重新驅動非常快速和簡單。
對於想要快速修復的人:
你可以在這里讀更多關於它的內容:
希望有人覺得這有幫助!
這是一個簡單的 python 腳本,您可以從 cli 使用它來執行相同的操作,僅取決於 boto3
用法
python redrive_messages __from_queue_name__ __to_queue_name__
代碼
import sys
import boto3
from src.utils.get_config.get_config import get_config
from src.utils.get_logger import get_logger
sqs = boto3.resource('sqs')
config = get_config()
log = get_logger()
def redrive_messages(from_queue_name:str, to_queue_name:str):
# initialize the queues
from_queue = sqs.get_queue_by_name(QueueName=from_queue_name)
to_queue = sqs.get_queue_by_name(QueueName=to_queue_name)
# begin querying for messages
should_check_for_more = True
messages_processed = []
while (should_check_for_more):
# grab the next message
messages = from_queue.receive_messages(MaxNumberOfMessages=1);
if (len(messages) == 0):
should_check_for_more = False;
break;
message = messages[0]
# requeue it
to_queue.send_message(MessageBody=message.body, DelaySeconds=0)
# let the queue know that the message was processed successfully
messages_processed.append(message)
message.delete()
print(f'requeued {len(messages_processed)} messages')
if __name__ == '__main__':
from_queue_name = sys.argv[1]
to_queue_name = sys.argv[2]
redrive_messages(from_queue_name, to_queue_name)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.