メッセージ キューの使用中には、考慮する必要がある実際的な問題が数多くあります。
1. メッセージの信頼性
メッセージの送信からコンシューマーによる受信まで、複数のプロセスを管理します。
これらの各手順はメッセージの損失につながる可能性があります。損失の一般的な理由は次のとおりです。
-
送信時に紛失しました:
-
プロデューサーが送信したメッセージが取引所に配信されませんでした
-
メッセージが交換局に到着した後、キューに到達しませんでした
-
-
MQ がダウンしているため、キューはメッセージを失います
-
コンシューマーはメッセージを受信した後、メッセージを消費せずにクラッシュします。
これらの問題に対して、RabbitMQ はそれぞれ次の解決策を提供します。
-
製作者確認の仕組み
-
mq 永続性
-
消費者確認の仕組み
-
失敗時の再試行メカニズム
2.プロデューサーメッセージ確認
RabbitMQ は、MQ に送信されたメッセージの損失を回避するための発行者確認メカニズムを提供します。このメカニズムでは、各メッセージに一意の ID を割り当てる必要があります。メッセージが MQ に送信されると、メッセージが正常に処理されたかどうかを示す結果が送信者に返されます。
結果を返す方法は 2 つあります。
-
発行者確認、送信者確認
-
メッセージはスイッチに正常に配信され、ack を返します。
-
メッセージは取引所に配信されませんでした。返品してください
-
-
発行者返送、差出人受領書
-
メッセージは交換局に配信されましたが、キューにはルーティングされませんでした。ACK とルーティング失敗の理由を返します。
-
2.1. 設定の変更
まず、発行者サービスの application.yml ファイルを変更し、次のコンテンツを追加します。
spring:
rabbitmq:
publisher-confirm-type: correlated
publisher-returns: true
template:
mandatory: true
例証します:
-
publish-confirm-type
: Publisher-confirm を有効にします。ここでは 2 つのタイプがサポートされています。-
simple
: タイムアウトになるまで確認結果を同期的に待ちます。 -
correlated
: 非同期コールバック、ContinueCallback を定義します。MQ が結果を返すときにこの confirmCallback がコールバックされます。
-
-
publish-returns
:publish-return 関数を有効にします。これもコールバック メカニズムに基づいていますが、ReturnCallback を定義します。 -
template.mandatory
: メッセージのルーティングが失敗した場合のポリシーを定義します。true、ReturnCallback を呼び出す、false: メッセージを直接破棄する
2.2. Return コールバックを定義する
各 RabbitTemplate は ReturnCallback を 1 つだけ設定できるため、プロジェクトのロード時に設定する必要があります。
パブリッシャー サービスを変更し、サービスを追加します。
@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取RabbitTemplate
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
// 设置ReturnCallback
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
// 投递失败,记录日志
log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
replyCode, replyText, exchange, routingKey, message.toString());
// 如果有业务需要,可以重发消息
});
}
}
2.3. confirmCallback の定義
成功または失敗を確認する各業務処理のロジックは必ずしも同じではないため、メッセージ送信時に confirmCallback を指定できます。
パブリッシャー サービスの cn.itcast.mq.spring.SpringAmqpTest クラスで、単体テスト メソッドを定義します。
public void testSendMessage2SimpleQueue() throws InterruptedException {
// 1.消息体
String message = "hello, spring amqp!";
// 2.全局唯一的消息ID,需要封装到CorrelationData中
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 3.添加callback
correlationData.getFuture().addCallback(
result -> {
if(result.isAck()){
// 3.1.ack,消息成功
log.debug("消息发送成功, ID:{}", correlationData.getId());
}else{
// 3.2.nack,消息失败
log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
}
},
ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
);
// 4.发送消息
rabbitTemplate.convertAndSend("task.direct", "task", message, correlationData);
// 休眠一会儿,等待ack回执
Thread.sleep(2000);
}
3. メッセージの永続性
プロデューサは、メッセージが RabbitMQ キューに配信できることを確認しますが、メッセージが RabbitMQ に送信された後、突然のダウンタイムが発生した場合、メッセージも失われる可能性があります。
メッセージが RabbitMQ に安全に保存されるようにするには、メッセージ永続化メカニズムを有効にする必要があります。
-
スイッチの永続性
-
キューの永続性
-
メッセージの永続性
3.1、スイッチの永続性
RabbitMQ のスイッチはデフォルトでは非永続的であり、mq の再起動後に失われます。
SpringAMQP では、スイッチの永続性をコードで指定できます。
@Bean
public DirectExchange simpleExchange(){
// 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
return new DirectExchange("simple.direct", true, false);
}
実際、デフォルトでは、Spring AMQP によって宣言されたスイッチは永続的です。
RabbitMQ コンソールの永続スイッチにこのマークが表示されますD
。
3.2、キューの永続性
RabbitMQ のキューはデフォルトでは非永続的であり、mq の再起動後に失われます。
SpringAMQP では、スイッチの永続性をコードで指定できます。
@Bean
public Queue simpleQueue(){
// 使用QueueBuilder构建队列,durable就是持久化的
return QueueBuilder.durable("simple.queue").build();
}
実際、デフォルトでは、Spring AMQP によって宣言されたキューは永続的です。
RabbitMQ コンソールの永続キューにこのマークが表示されますD
。
3.3、メッセージの永続化
SpringAMQP を使用してメッセージを送信する場合、メッセージのプロパティ (MessageProperties) を設定し、配信モードを指定できます。
-
非永続
-
持久化
Javaコードで指定します。
デフォルトでは、Spring AMQP によって送信されるメッセージは、指定しなくても永続的です。
4. 消費者メッセージの確認
RabbitMQ は、メッセージを読み取った後に書き込むメカニズムであり、メッセージがコンシューマーによって消費された後、すぐに削除されることを確認します。
RabbitMQ は、コンシューマがコンシューマ レシートを通じてメッセージを正常に処理したかどうかを確認します。コンシューマはメッセージを取得した後、RabbitMQ に ACK レシートを送信して、メッセージが処理されたことを示す必要があります。
次のシナリオを想像してください。
-
1) RabbitMQ はコンシューマにメッセージを配信します
-
2) コンシューマーはメッセージを受信した後、RabbitMQ に ACK を返します。
-
3) RabbitMQ 削除メッセージ
-
4) コンシューマがダウンしており、メッセージが処理されていない
このようにして、メッセージは失われます。したがって、コンシューマが ACK を返すタイミングは非常に重要です。
SpringAMQP では、次の 3 つの確認モードを構成できます。
- 手動: 手動 ACK。ビジネス コードの終了後に API を呼び出して ACK を送信する必要があります。
- auto: 自動 ack、リスナー コードは Spring によって監視され、例外があるかどうかが確認されます。例外がなければ ack を返し、例外がスローされた場合は nack を返します。
- none: close ack、MQ はコンシューマーがメッセージを取得した後に正常に処理すると想定するため、メッセージは配信後すぐに削除されます。
このことから次のことがわかります。
-
なしモードでは、メッセージ配信の信頼性が低く、失われる可能性があります
-
auto モードはトランザクション メカニズムに似ており、例外が発生すると nack を返し、メッセージは mq にロールバックされ、例外がなければ ack を返します。
-
マニュアル:ビジネスの状況に応じて、いつ確認するかを判断する
通常は、デフォルトの auto を使用できます。
4.1、デモなしモード
コンシューマ サービスの application.yml ファイルを変更し、次のコンテンツを追加します。
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: none # 关闭ack
コンシューマ サービスの SpringRabbitListener クラスのメソッドを変更して、メッセージ処理例外をシミュレートします。
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg) {
log.info("消费者接收到simple.queue的消息:【{}】", msg);
// 模拟异常
System.out.println(1 / 0);
log.debug("消息处理完成!");
}
このテストでは、メッセージ処理で例外がスローされた場合でも、メッセージは RabbitMQ によって削除されることがわかります。
4.2. デモ自動モード
確認メカニズムを再度自動に変更します。
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto # 关闭ack
異常な位置でポイントをブレークし、メッセージを再送信します。プログラムがブレークポイントでスタックすると、メッセージのステータスが unack (未確定ステータス) であることがわかります。
例外がスローされた後、Spring は自動的に nack を返すため、メッセージは Ready 状態に戻り、RabbitMQ によって削除されません。
5. 消費失敗リトライ機構
コンシューマに例外が発生すると、メッセージは引き続きキューに再キューイング (キューに再入力) され、その後コンシューマに再送信されます。その後、再度例外が発生し、再びキューに再登録されるという無限ループが発生し、mq のメッセージ処理が発生します。急上昇し、不必要な圧力をもたらします。
5.1、ローカルリトライ
Spring の再試行メカニズムを使用すると、コンシューマで例外が発生したときに、mq キューに無制限に再キューするのではなく、ローカルな再試行を使用できます。
コンシューマ サービスの application.yml ファイルを変更し、コンテンツを追加します。
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true # 开启消费者失败重试
initial-interval: 1000 # 初识的失败等待时长为1秒
multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
max-attempts: 3 # 最大重试次数
stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
コンシューマ サービスを再起動し、前のテストを繰り返します。それは次のように見つかります。
-
3 回再試行すると、SpringAMQP は例外 AmqpRejectAndDontRequeueException をスローし、ローカル再試行がトリガーされたことを示します。
-
RabbitMQ コンソールを確認して、メッセージが削除されていることを確認します。これは、SpringAMQP が最後に ack を返し、mq がメッセージを削除したことを示しています。
結論は:
-
ローカル再試行が有効な場合、メッセージ処理中に例外がスローされた場合、その例外はキューに要求されませんが、コンシューマーによってローカルで再試行されます。
-
最大再試行回数に達すると、Spring は ack を返し、メッセージは破棄されます。
5.2. 失敗戦略
前のテストでは、最大再試行回数に達すると、メッセージは破棄されます。これは Spring の内部メカニズムによって決定されます。
再試行モードをオンにすると、再試行回数が制限されます。メッセージがまだ失敗する場合は、それを処理するために MessageRecovery インターフェイスが必要です。これには、次の 3 つの異なる実装が含まれています。
-
RejectAndDontRequeueRecoverer: 再試行が完了した後、メッセージを直接拒否して破棄します。これがデフォルトです
-
ImmediateRequeueMessageRecoverer: 再試行が完了すると、nack が返され、メッセージが再度キューに入れられます。
-
RepublishMessageRecoverer: 再試行が完了した後、指定された交換に失敗メッセージを配信します。
より洗練されたソリューションは RepublishMessageRecoverer で、失敗後、メッセージは例外メッセージの保存専用の指定されたキューに配信され、その後の処理は手動で行われます。
1) コンシューマ サービスで失敗したメッセージを処理するためのスイッチとキューを定義する
@Bean
public DirectExchange errorMessageExchange(){
return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){
return new Queue("error.queue", true);
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
}
2) RepublishMessageRecoverer を定義し、キューとスイッチを関連付ける
@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}
完全なコード:
@Configuration
public class ErrorMessageConfig {
@Bean
public DirectExchange errorMessageExchange(){
return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){
return new Queue("error.queue", true);
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
}
@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}
}
6. まとめ
RabbitMQ メッセージの信頼性を確保するにはどうすればよいですか?
-
プロデューサー確認メカニズムを有効にして、プロデューサーのメッセージがキューに到達できることを確認します。
-
永続化機能を有効にして、メッセージが消費される前にキュー内で失われないようにします。
-
コンシューマ確認メカニズムを自動としてオンにすると、スプリングはメッセージが正常に処理されたことを確認した後に確認を完了します。
-
コンシューマ障害の再試行メカニズムを有効にし、MessageRecoverer を設定します。複数回の再試行が失敗すると、メッセージは異常なスイッチに配信され、手動処理に引き渡されます。