Amazon SQS에서 DLQ에서 메시지를 이동하는 가장 좋은 방법은 무엇입니까?


88

배달 못한 편지 대기열에서 Amazon SQS의 원래 대기열로 메시지를 다시 이동하는 모범 사례는 무엇입니까?

일 것이다

  1. DLQ에서 메시지 받기
  2. 대기열에 메시지 쓰기
  3. DLQ에서 메시지 삭제

아니면 더 간단한 방법이 있습니까?

또한 AWS는 결국 콘솔에 DLQ에서 메시지를 이동하는 도구를 갖게 될까요?



답변:


135

다음은 빠른 해킹입니다. 이것은 확실히 최선이나 권장 옵션이 아닙니다.

  1. 최대 수신이 1 인 실제 DLQ에 대한 DLQ로 기본 SQS 대기열을 설정합니다.
  2. DLQ의 콘텐츠보기 (실제 DLQ에 대한 DLQ이므로 메시지가 기본 대기열로 이동 됨)
  3. 기본 대기열이 실제 DLQ의 DLQ가되지 않도록 설정을 제거하십시오.

12
당신은 당신이 무슨 일을하고 있는지 알고 있고이 적절한 방법의 #yolo 해결하기 위해 시간이없는 경우지만 빠른 수정을위한 좋은 옵션 - 그래, 아주 많이 해킹이다
토마스 왓슨

14
그러나 이렇게하면 수신 횟수가 0으로 재설정되지 않습니다. 조심해.
Rajdeep Siddhapura

1
올바른 접근 방식은 SQS에서 최대 수신 횟수로 Redrive 정책을 구성하는 것이며, 설정된 수신 횟수를 넘을 때 메시지를 자동으로 DLQ로 이동 한 다음 DLQ에서 읽을 리더 스레드를 작성합니다.
Ash

5
당신은 천재입니다.
JefClaes

1
저는 몇 달 전에이
MaltMaster 19

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를 터미널에서 이것을 실행 속성 집합을 바르 :npx replay-aws-dlq DL_URI MAIN_URI
Vasyl Boroviak

오타 참고 : dql-> dlq # install npm install replay-aws-dlq;
Lee Oades

이것은 나를 위해 완벽하게 작동했습니다 (참고, 나는 go 기반의 것을 시도했습니다). 메시지를 한꺼번에 이동하지 않고 단계적으로 이동하는 것처럼 보였고 (좋은 점), 진행률 표시 줄도있었습니다. 허용되는 답변 IMO보다 낫습니다.
Yevgeny Ananin 20.11. 06

Lambda를 사용하여 주어진 작업을 수행하는 최근 AWS 블로그 게시물이 있습니다. 또한 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 기능을 잃게됩니다. 새 메시지로 처리됩니다.
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()

이 파이썬인가요?
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이므로 실제 SQS_Queue 큐인 자체 dlq로 모든 메시지를 보냅니다.

그것은 DLQ를 유지하려는 목적을 무너 뜨릴 것입니다. DLQ는 나중에이를 수행 할 수 있도록 실패를 관찰 할 때 시스템을 과도하게로드하지 않도록 설계되었습니다.
Buddha

1
확실히 목적을 무너 뜨리고 확장, 조절 및 카운트 수신과 같은 다른 이점을 얻을 수 없습니다. 또한 일반 큐를 처리 큐로 사용해야하며 메시지 수신 횟수가 'N'에 도달하면 DLQ로 이동해야합니다. 이것이 이상적으로 구성되어야하는 것입니다.
Ash

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를 사용하여이를 수행하기 위해 작은 파이썬 스크립트를 작성했습니다.

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필드를 제공 할 수있을뿐만 아니라 fifo 대기열을 지원 합니다.


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

1

DLQ는 원래 소비자가 다양한 시도 후에도 메시지를 성공적으로 소비하지 못한 경우에만 작동합니다. 우리는 여전히 무언가를 할 수 있다고 믿고 (다시 처리하거나 기록하거나 일부 통계를 수집 할 수 있다고 믿기 때문에) 메시지를 삭제하고 싶지 않으며이 메시지를 계속해서 만나고 기능을 중지하고 싶지 않습니다. 이 뒤에 다른 메시지를 처리합니다.

DLQ는 다른 대기열 일뿐입니다. 즉, DLQ에서 소비하고 원래 대기열로 메시지를 다시 생성하고 DLQ에서 삭제하는 이상적으로 덜 자주 실행되는 (원래 대기열에 비해) DLQ에 대한 소비자를 작성해야합니다. 이것이 의도 한 동작이고 우리가 생각하는 경우 원래 소비자는 이제 다시 처리 할 준비가되었습니다. 메시지를 잃지 않고 수동으로 검사하고 필요한 변경을 수행하고 원래 소비자의 다른 버전을 배포 할 수있는 기회를 얻었으므로이주기가 한동안 계속 되어도 괜찮을 것입니다 (물론 메시지 보존 기간은 4 일 이내입니다). 기본).

AWS가이 기능을 기본적으로 제공하지만 아직 보이지 않는다면 좋을 것입니다. 최종 사용자가 적절하다고 생각하는 방식으로 사용하도록이 기능을 맡기고 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.