Amazon SQSでメッセージをDLQから移動する最良の方法は?


88

メッセージをデッドレターキューからAmazonSQSの元のキューに戻すためのベストプラクティスは何ですか?

それでしょうか

  1. DLQからメッセージを取得する
  2. キューにメッセージを書き込む
  3. DLQからメッセージを削除します

それとももっと簡単な方法はありますか?

また、AWSは最終的にコンソールにメッセージをDLQから移動するためのツールを備えていますか?


github.com/garryyao/replay-aws-dlqはかなりうまく機能します
UladKasach19年

回答:


135

これが簡単なハックです。これは間違いなく最良または推奨されるオプションではありません。

  1. メインSQSキューを、最大受信数が1の実際のDLQのDLQとして設定します。
  2. DLQのコンテンツを表示します(これは実際のDLQのDLQであるため、メッセージをメインキューに移動します)
  3. メインキューが実際のDLQのDLQにならないように、設定を削除します

12
ええ、これは非常にハックです-しかし、あなたが何をしているのかを知っていて、これを適切な方法で解決する時間がない場合は、迅速な修正のための素晴らしいオプションです#yolo
Thomas Watson

14
ただし、これを行っても受信カウントは0にリセットされません。注意してください。
Rajdeep Siddhapura 2017

1
正しいアプローチは、最大受信数でSQSのリドライブポリシーを設定することです。設定された受信数を超えると、メッセージは自動的にDLQに移動し、DLQから読み取るリーダースレッドを書き込みます。
アッシュ

5
あなたは天才です。
JefClaes

1
私は数ヶ月前にこの問題のCLIツールを作成しました:github.com/renanvieira/phoenix-letter
MaltMaster

15

これを行うスクリプトがいくつかあります。

  • npm / nodejsベース:http://github.com/garryyao/replay-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] 

1
受け入れられた答えとは異なり、これは最も簡単な方法です。ただ、プロパティセットをvarsのENV AWSを持って、端末からこれを実行しますnpx replay-aws-dlq DL_URI MAIN_URI
ワシルBoroviak

タイプミスに注意してください:dql-> dlq#install npm install replay-aws-dlq;
LeeOades20年

これは私にとって完璧に機能しました(注:私はgoベースのものだけを試しました)。メッセージを段階的に移動するように見えましたが、一度にすべてではなく(良いことです)、プログレスバーさえありました。受け入れられた答えIMOよりも優れています。
YevgenyAnanin20年

Lambdaを使用して特定のタスクを実行する最近のAWSブログ投稿があります。AWSサーバーレスアプリリポジトリでも公開されています:aws.amazon.com/blogs/compute/… (上記の簡単なハックに行くので、試していませんが、これは道のりのようです)
TH-

13

メッセージの重複、回復シナリオ、メッセージの喪失、重複排除チェックなど、他にも多くの課題が発生するため、メッセージを移動する必要はありません。

これが私たちが実装したソリューションです-

通常、DLQは、永続的なエラーではなく、一時的なエラーに使用します。だから以下のアプローチを取った-

  1. 通常のキューのようにDLQからのメッセージを読む

    利点
    • 重複したメッセージ処理を回避するには
    • DLQのより良い制御-私がチェックしたように、通常のキューが完全に処理されたときにのみ処理します。
    • DLQのメッセージに基づいてプロセスをスケールアップします
  2. 次に、通常のキューがフォローしているのと同じコードに従います。

  3. ジョブを中止したり、処理中にプロセスが終了した場合(インスタンスの強制終了やプロセスの終了など)の信頼性が向上します

    利点
    • コードの再利用性
    • エラー処理
    • 回復とメッセージの再生
  4. 他のスレッドがメッセージを処理しないように、メッセージの可視性を拡張します。

    メリット
    • 複数のスレッドで同じレコードを処理することは避けてください。
  5. 永続的なエラーまたは成功した場合にのみ、メッセージを削除してください。

    メリット
    • 一時的なエラーが発生するまで処理を続けます。

私はあなたのアプローチが本当に好きです!この場合、「永続的なエラー」をどのように定義しますか?
DMac theDestroyer18年

HTTPステータスコード> 200 <500より大きいものは、永続的なエラーです
Ash

これは確かに本番環境での優れたアプローチです。ただし、この投稿は、DLQから通常のキューにメッセージを再投稿する方法を単に尋ねていると思います。自分が何をしているのかを知っていると便利なことがあります。
linehrr 2018

それはあなたがそれをすべきではないと私が言っていることです。あなたがそれをするならば、それはより多くの問題を引き起こすからです。他のメッセージプッシュと同じようにメッセージを移動できますが、受信数、可視性などのDLQ機能は失われます。新しいメッセージとして扱われます。
アッシュ

6

それはあなたの最良の選択肢のように見えます。手順2の後でプロセスが失敗する可能性があります。その場合、メッセージを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()

これはPythonですか?
carlin.scott 2017

python2実際
KristofJozsa19年

4

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なので、すべてのメッセージを実際のSQS_Queueキューである独自のdlqに送信します。

それはDLQを維持する目的を無効にします。DLQは、障害を監視しているときにシステムに過負荷をかけないようにすることを目的としているため、後でこれを行うことができます。
仏陀

1
それは間違いなく目的を打ち負かし、スケールアップ、スロットル、受信カウントなどの他の利点を達成することはできません。さらに、通常のキューを処理キューとして使用する必要があり、メッセージの受信カウントが「N」に達すると、DLQに移動する必要があります。これは理想的には構成する必要があるものです。
アッシュ

3
多くのメッセージを再駆動するための1回限りのソリューションとして、これは魅力のように機能します。ただし、長期的な解決策としては適切ではありません。
nmio 2018

はい、これは(メインキューの問題を修正した後)メッセージを再駆動するための1回限りのソリューションとして非常に価値があります。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

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フィールドを指定することもできます。


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.