web-dev-qa-db-ja.com

Spring非同期MessageListenerユースケースでビジネス例外が発生したときにRabbitMQに再試行を依頼する方法

Spring AMQPメッセージリスナーを実行しています。

public class ConsumerService implements MessageListener {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public void onMessage(Message message) {
        try {
            testService.process(message); //This process method can throw Business Exception
        } catch (BusinessException e) {
           //Here we can just log the exception. How the retry attempt is made?
        } catch (Exception e) {
           //Here we can just log the exception.  How the retry attempt is made?
        }
    }
}

ご覧のとおり、処理中に例外が発生する可能性があります。 Catchブロックの特定のエラーのため、再試行したい。 onMessageで例外を通過できません。例外があることをRabbitMQに伝えて再試行する方法は?

17
Santosh

onMessage()は、チェックされた例外をスローすることを許可しないため、RuntimeExceptionで例外をラップして、再スローできます。

_try {
    testService.process(message);
} catch (BusinessException e) {
    throw new RuntimeException(e);
}
_

ただし、これによりメッセージが無期限に再配信される可能性があることに注意してください。これはどのように機能するかです:

RabbitMQはメッセージの拒否とブローカーに再キューイングを要求することをサポートしています。これを示します here 。ただし、RabbitMQには、再試行ポリシーのメカニズムが本来備わっていません。最大再試行、​​遅延などの設定.

Spring AMQPを使用する場合、「拒否時に再キュー」がデフォルトのオプションです。 Springの SimpleMessageListenerContainer は、未処理の例外がある場合、デフォルトでこれを実行します。したがって、あなたのケースでは、キャッチされた例外を再スローする必要があります。ただし、メッセージを処理できず、常に例外をスローする場合、これは無期限に再配信され、無限ループが発生することに注意してください。

AmqpRejectAndDontRequeueException 例外をスローすることで、メッセージごとにこの動作をオーバーライドできます。この場合、メッセージは再キューイングされません。

SimpleMessageListenerContainerの「requeue on reject」動作を完全にオフにすることもできます。

_container.setDefaultRequeueRejected(false) 
_

メッセージが拒否されて再キューイングされない場合、RabbitMQでメッセージが設定されていると、メッセージは失われるか、DLQに転送されます。

最大試行回数、遅延などの再試行ポリシーが必要な場合、最も簡単なのは、再試行ごとにメッセージを拒否せずに(Thread.sleep()を使用して)スレッド内ですべての再試行を実行する「ステートレス」なRetryOperationsInterceptorを設定することです(したがって、再試行するたびにRabbitMQに戻る必要はありません)。再試行が終了すると、デフォルトで警告がログに記録され、メッセージが消費されます。 DLQに送信する場合は、 RepublishMessageRecoverer または再キューイングせずにメッセージを拒否するカスタム MessageRecoverer が必要です(後者の場合は setup キュー上のRabbitMQ DLQ)。デフォルトのメッセージ回復機能の例:

_container.setAdviceChain(new Advice[] {
        org.springframework.amqp.rabbit.config.RetryInterceptorBuilder
                .stateless()
                .maxAttempts(5)
                .backOffOptions(1000, 2, 5000)
                .build()
});
_

これには、再試行の全期間にわたってスレッドを占有するという欠点があります。 「ステートフル」RetryOperationsInterceptorを使用するオプションもあります。これにより、再試行ごとにメッセージがRabbitMQに返されますが、アプリケーション内のThread.sleep()で遅延が実装され、さらにステートフルインターセプターが設定されます。もう少し複雑です。

したがって、Threadを使用せずに遅延を伴う再試行が必要な場合は、RabbitMQキューでTTLを使用した、より複雑なカスタムソリューションが必要になります。指数バックオフが必要ない場合(遅延がこのようなソリューションを実装するには、基本的にはrabbitMQに引数_"x-message-ttl": <delay time in milliseconds>_および_"x-dead-letter-exchange":"<name of the original queue>"_を使用して別のキューを作成します。次に、メインキューで_"x-dead-letter-exchange":"<name of the queue with the TTL>"_を設定します。したがって、メッセージを拒否してキューに再登録しないと、RabbitMQは2番目のキューにリダイレクトします。TTLが期限切れになると、元のキューにリダイレクトされ、アプリケーションに再配信されます。失敗のたびにRabbitMQへのメッセージを拒否し、再試行回数も追跡する再試行インターセプターが必要です。アプリケーションの状態を維持する必要を回避するには(アプリケーションがクラスター化されている場合は状態を複製する必要があるため)、 RabbitMQが設定する_x-death_ヘッダーからの再試行回数。moを参照このヘッダーについての情報 here 。そのため、その時点で、カスタムインターセプターの実装は、この動作でSpringステートフルインターセプターをカスタマイズするよりも簡単です。

また、 Spring AMQPリファレンスの再試行に関するセクション も確認してください。

30
Nazaret K.