web-dev-qa-db-ja.com

Spring AMQPでAckまたはNackを使用する方法

Spring AMQPは初めてです。プロデューサーであるアプリケーションが、コンシューマーである他のアプリケーションにメッセージを送信しています。

コンシューマーがメッセージを受信したら、データの検証を行います。

データが適切な場合はACKを実行し、メッセージをキューから削除する必要があります。データが不適切な場合は、データをNACK(Negative Acknowledge)して、RabbitMQでキューに再登録する必要があります

私は遭遇しました

**factory.setDefaultRequeueRejected(false);**(メッセージを再キューイングしません)

**factory.setDefaultRequeueRejected(true);**(例外が発生するとメッセージをキューに再登録します)

しかし、私の場合、検証に基づいてメッセージを確認します。次に、メッセージを削除します。 NACKの場合、メッセージを再キューイングします。

RabbitMQウェブサイトで読みました

AMQP仕様では、クライアントが個々の配信されたメッセージを拒否し、ブローカーにそれらを破棄するか再キューイングするように指示することを可能にするbasic.rejectメソッドを定義しています

上記のシナリオをどのように達成しますか?いくつか例を挙げてください。

小さなプログラムを試しました

       logger.info("Job Queue Handler::::::::::" + new Date());
        try {

        }catch(Exception e){

            logger.info("Activity Object Not Found Exception so message should be Re-queued the Message::::::::::::::");

        }

        factory.setErrorHandler(new ConditionalRejectingErrorHandler(cause ->{
            return cause instanceof XMLException;
        }));

メッセージは別の例外のために再キューイングされていませんfactory.setDefaultRequeueRejected(true)

09:46:38,854エラー[stderr](SimpleAsyncTaskExecutor-1)org.activiti.engine.ActivitiObjectNotFoundException:キー「WF89012」でプロセスがデプロイされていません

09:46:39,102 INFO [com.example.bip.rabbitmq.handler.ErrorQueueHandler](SimpleAsyncTaskExecutor-1)エラーキューから受信:{ERROR = JPAトランザクションをコミットできませんでした。ネストされた例外はjavax.persistence.RollbackException:rollbackOnlyとしてマークされたトランザクション}

10
Chandan

ドキュメント を参照してください。

デフォルトでは、(defaultRequeueRejected=trueを使用して)リスナーは、リスナーが正常に終了した場合はメッセージを確認し(削除される)、リスナーが例外をスローした場合はメッセージを拒否(および再キューイング)します。

リスナー(またはエラーハンドラー)がAmqpRejectAndDontRequeueExceptionをスローすると、デフォルトの動作が上書きされ、メッセージは破棄されます(または、構成されている場合はDLX/DLQにルーティングされます)-コンテナーはbasicReject(false)を呼び出しますbasicReject(true)の代わりに。

したがって、検証が失敗した場合は、AmqpRejectAndDontRequeueExceptionをスローします。または、カスタムエラーハンドラーを使用してリスナーを構成し、例外をAmqpRejectAndDontRequeueExceptionに変換します。

それは この答え で説明されています。

自分を攻撃する責任を本当に負う必要がある場合は、確認モードをMANUALに設定し、ChannelAwareMessageListenerまたは@RabbitListenerを使用している場合は この手法 を使用します。

しかし、ほとんどの人はコンテナに物事を任せるだけです(いったん何が起こっているのか理解したら)。一般に、手動ackの使用は、ackの延期や早期のackなどの特殊なユースケース用です。

[〜#〜]編集[〜#〜]

指摘した答えに誤りがありました(現在は修正済み)。 ListenerExecutionFailedExceptionの原因を調べる必要があります。私はこれをテストしましたが、期待どおりに動作します...

@SpringBootApplication
public class So39530787Application {

    private static final String QUEUE = "So39530787";

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(So39530787Application.class, args);
        RabbitTemplate template = context.getBean(RabbitTemplate.class);
        template.convertAndSend(QUEUE, "foo");
        template.convertAndSend(QUEUE, "bar");
        template.convertAndSend(QUEUE, "baz");
        So39530787Application bean = context.getBean(So39530787Application.class);
        bean.latch.await(10, TimeUnit.SECONDS);
        System.out.println("Expect 1 foo:"  + bean.fooCount);
        System.out.println("Expect 3 bar:"  + bean.barCount);
        System.out.println("Expect 1 baz:"  + bean.bazCount);
        context.close();
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setErrorHandler(new ConditionalRejectingErrorHandler(
                t -> t instanceof ListenerExecutionFailedException && t.getCause() instanceof FooException));
        return factory;
    }

    @Bean
    public Queue queue() {
        return new Queue(QUEUE, false, false, true);
    }
    private int fooCount;

    private int barCount;

    private int bazCount;

    private final CountDownLatch latch = new CountDownLatch(5);

    @RabbitListener(queues = QUEUE)
    public void handle(String in) throws Exception {
        System.out.println(in);
        latch.countDown();
        if ("foo".equals(in) && ++this.fooCount < 3) {
            throw new FooException();
        }
        else if ("bar".equals(in) && ++this.barCount < 3) {
            throw new BarException();
        }
        else if ("baz".equals(in)) {
            this.bazCount++;
        }
    }

    @SuppressWarnings("serial")
    public static class FooException extends Exception { }

    @SuppressWarnings("serial")
    public static class BarException extends Exception { }

}

結果:

Expect 1 foo:1
Expect 3 bar:3
Expect 1 baz:1
12
Gary Russell