วิธีที่ดีที่สุดในการย้ายข้อความออกจาก DLQ ใน Amazon SQS?


88

แนวทางปฏิบัติที่ดีที่สุดในการย้ายข้อความจากคิวจดหมายตายกลับไปยังคิวเดิมใน Amazon SQS คืออะไร

มันจะเป็นอย่างไร

  1. รับข้อความจาก DLQ
  2. เขียนข้อความถึงคิว
  3. ลบข้อความจาก DLQ

หรือมีวิธีที่ง่ายกว่านี้?

ในที่สุด AWS จะมีเครื่องมือในคอนโซลเพื่อย้ายข้อความออกจาก DLQ หรือไม่


github.com/garryyao/replay-aws-dlqใช้ได้ดีทีเดียว
Ulad Kasach

อีกทางเลือกหนึ่งของgithub.com/mercury2269/sqsmover
Sergey

คำตอบ:


135

นี่คือการแฮ็กอย่างรวดเร็ว นี่ไม่ใช่ตัวเลือกที่ดีที่สุดหรือแนะนำแน่นอน

  1. ตั้งค่าคิว SQS หลักเป็น DLQ สำหรับ DLQ จริงโดยมี Maximum Receives เป็น 1
  2. ดูเนื้อหาใน DLQ (สิ่งนี้จะย้ายข้อความไปยังคิวหลักเนื่องจากเป็น DLQ สำหรับ DLQ จริง)
  3. ลบการตั้งค่าเพื่อไม่ให้คิวหลักเป็น DLQ ของ DLQ จริงอีกต่อไป

12
ใช่นี่เป็นการแฮ็กอย่างมาก แต่เป็นตัวเลือกที่ดีสำหรับการแก้ไขอย่างรวดเร็วหากคุณรู้ว่ากำลังทำอะไรอยู่และไม่มีเวลาแก้ปัญหานี้ด้วยวิธีที่ถูกต้อง #yolo
Thomas Watson

14
แต่จำนวนรับจะไม่รีเซ็ตเป็น 0 เมื่อคุณทำเช่นนี้ ระวัง.
Rajdeep Siddhapura

1
แนวทางที่ถูกต้องคือการกำหนดค่านโยบาย Redrive ใน SQS ด้วยจำนวนการรับสูงสุดและจะย้ายข้อความไปยัง DLQ โดยอัตโนมัติเมื่อข้ามจำนวนการรับชุดจากนั้นเขียนเธรดผู้อ่านเพื่ออ่านจาก DLQ
เถ้า

5
คุณเป็นอัจฉริยะ
JefClaes

1
ฉันสร้างเครื่องมือ CLI สำหรับปัญหานี้เมื่อสองสามเดือนก่อน: github.com/renanvieira/phoenix-letter
MaltMaster

15

มีสคริปต์บางส่วนที่ทำสิ่งนี้ให้คุณ:

# 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 ติดตั้ง replay-aws-dlq;
Lee Oades

สิ่งนี้ใช้งานได้อย่างไม่มีที่ติสำหรับฉัน (โปรดทราบว่าฉันลองใช้วิธีเดียวเท่านั้น) ดูเหมือนว่าจะย้ายข้อความเป็นขั้นตอนและไม่ใช่ทั้งหมดในคราวเดียว (เป็นสิ่งที่ดี) และยังมีแถบความคืบหน้า ดีกว่า IMO คำตอบที่ยอมรับ
Yevgeny Ananin

มีบล็อกโพสต์ล่าสุดของ AWS ที่ใช้ Lambda เพื่อทำงานที่กำหนดให้สำเร็จ นอกจากนี้ยังเผยแพร่ในที่เก็บแอป AWS แบบไร้เซิร์ฟเวอร์: aws.amazon.com/blogs/compute/… (ฉันยังไม่ได้ลองใช้เพราะฉันจะไปแฮ็คด่วนด้านบน แต่ดูเหมือนจะเป็นวิธีที่จะไป)
th-

13

ไม่จำเป็นต้องย้ายข้อความเพราะมันจะมาพร้อมกับความท้าทายอื่น ๆ อีกมากมายเช่นข้อความที่ซ้ำกันสถานการณ์การกู้คืนข้อความสูญหายการตรวจสอบการคัดลอกและอื่น ๆ

นี่คือโซลูชันที่เรานำมาใช้ -

โดยปกติเราใช้ DLQ สำหรับข้อผิดพลาดชั่วคราวไม่ใช่ข้อผิดพลาดถาวร ดังนั้นใช้แนวทางด้านล่าง -

  1. อ่านข้อความจาก DLQ เหมือนคิวปกติ

    สิทธิประโยชน์
    • เพื่อหลีกเลี่ยงการประมวลผลข้อความซ้ำ
    • ควบคุม DLQ ได้ดีขึ้น - เหมือนที่ฉันใส่เช็คเพื่อประมวลผลเมื่อคิวปกติได้รับการประมวลผลอย่างสมบูรณ์เท่านั้น
    • ปรับขนาดกระบวนการตามข้อความบน DLQ
  2. จากนั้นทำตามรหัสเดียวกันกับคิวปกติที่ตามมา

  3. มีความน่าเชื่อถือมากขึ้นในกรณีที่มีการยกเลิกงานหรือกระบวนการถูกยกเลิกในขณะดำเนินการ (เช่นอินสแตนซ์ถูกฆ่าหรือสิ้นสุดกระบวนการ)

    สิทธิประโยชน์
    • การใช้รหัสซ้ำ
    • การจัดการข้อผิดพลาด
    • การกู้คืนและการเล่นซ้ำข้อความ
  4. ขยายการแสดงข้อความเพื่อไม่ให้เธรดอื่นประมวลผล

    ประโยชน์
    • หลีกเลี่ยงการประมวลผลเร็กคอร์ดเดียวกันโดยหลายเธรด
  5. ลบข้อความก็ต่อเมื่อมีข้อผิดพลาดถาวรหรือสำเร็จ

    ประโยชน์
    • ดำเนินการต่อไปจนกว่าเราจะได้รับข้อผิดพลาดชั่วคราว

ฉันชอบแนวทางของคุณมาก! คุณกำหนด "ข้อผิดพลาดถาวร" ในกรณีนี้อย่างไร?
DMac the Destroyer

สิ่งที่มากกว่ารหัสสถานะ HTTP> 200 <500 เป็นข้อผิดพลาดถาวร
Ash

นี่เป็นแนวทางที่ดีในการผลิต อย่างไรก็ตามฉันคิดว่าโพสต์นี้ขอเพียงวิธีการโพสต์ข้อความจาก DLQ ไปยังคิวปกติ ซึ่งบางครั้งก็มีประโยชน์หากคุณรู้ว่ากำลังทำอะไรอยู่
linehrr

นั่นคือสิ่งที่ฉันกำลังบอกว่าคุณไม่ควรทำ เพราะหากทำแล้วจะยิ่งสร้างปัญหา เราสามารถย้ายข้อความได้เหมือนกับการพุชข้อความอื่น ๆ แต่จะสูญเสียฟังก์ชัน DLQ เช่นจำนวนรับการมองเห็นและทั้งหมด จะถือว่าเป็นข้อความใหม่
เถ้า

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()

นี่คืองูหลาม?
carlin.scott

python2 จริง
Kristof Jozsa

4

มีอีกวิธีหนึ่งในการบรรลุเป้าหมายนี้โดยไม่ต้องเขียนโค้ดแม้แต่บรรทัดเดียว พิจารณาชื่อคิวจริงของคุณคือ SQS_Queue และ DLQ คือ SQS_DLQ ตอนนี้ทำตามขั้นตอนเหล่านี้:

  1. ตั้งค่า SQS_Queue เป็น dlq ของ SQS_DLQ เนื่องจาก SQS_DLQ เป็น dlq ของ SQS_Queue อยู่แล้ว ตอนนี้ทั้งสองทำหน้าที่เป็น dlq ของอีกฝ่าย
  2. ตั้งค่าจำนวนรับสูงสุดของ SQS_DLQ ของคุณเป็น 1
  3. ตอนนี้อ่านข้อความจากคอนโซล SQS_DLQ เนื่องจากจำนวนการรับข้อความคือ 1 ข้อความจะส่งข้อความทั้งหมดไปยัง dlq ของตัวเองซึ่งเป็นคิว SQS_Queue จริงของคุณ

นั่นจะเป็นการเอาชนะวัตถุประสงค์ของการรักษา DLQ DLQ มีไว้เพื่อไม่ให้โหลดระบบของคุณมากเกินไปเมื่อคุณสังเกตเห็นความล้มเหลวเพื่อให้คุณสามารถดำเนินการนี้ได้ในภายหลัง
พระพุทธเจ้า

1
มันจะพ่ายแพ้จุดประสงค์อย่างแน่นอนและคุณจะไม่สามารถบรรลุผลประโยชน์อื่น ๆ เช่นการเพิ่มขนาดการควบคุมปริมาณและการนับจำนวน ยิ่งไปกว่านั้นคุณควรใช้คิวปกติเป็นคิวการประมวลผลและหากข้อความที่ได้รับมีค่าเป็น '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
แพทริคฟินนิกัน

3

ฉันเขียนสคริปต์ python ขนาดเล็กเพื่อทำสิ่งนี้โดยใช้ boto3 lib:

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ฟิลด์


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 มอบความสามารถนี้ให้นอกกรอบ แต่ฉันยังไม่เห็น - พวกเขาปล่อยให้ผู้ใช้ปลายทางใช้มันในแบบที่พวกเขารู้สึกว่าเหมาะสม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.