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としてマークされたトランザクション}
ドキュメント を参照してください。
デフォルトでは、(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