インタビュアーはよく尋ねます:Rocket MQは、ニュースが失われないようにするにはどうすればよいですか?
私が言いたいのは:これはそれですか?私は笑った。
1.メッセージ送信プロセス
メッセージフローは次の3つの部分に分けられ、それぞれがデータを失う可能性があります。
- 生産段階:プロデューサーはネットワークを介してブローカーにメッセージを送信します。到達不能なネットワーク遅延など、この送信が失われる可能性があります。
- ストレージ段階:ブローカーは最初にメッセージをメモリに入れ、次にフラッシュ戦略に従ってハードディスクに永続化する必要があります。メッセージはプロデューサーから受信されたばかりで、メモリにありますが、メッセージはダウンしています。異常なため、メッセージが失われます。
- 消費段階:消費の失敗は、実際にはメッセージ損失の一種です。
2.プロデューサーの生産段階
プロデューサーはネットワークを介してブローカーにメッセージを送信します。到達不能なネットワーク遅延など、この送信が失われる可能性があります。
1.ソリューション1
1.1説明
送信方法には、同期送信、非同期送信、一方向送信の3つがあります。メッセージを同期的に送信できます。メッセージを送信するときは、ブローカーから返される結果を同期的にブロックして待機します。成功しなかった場合、SendResultを受信しません。これが最も信頼性の高い方法です。2つ目は非同期送信であり、コールバックメソッドで送信が成功したかどうかを知ることができます。一方向送信(OneWay)は最も信頼性の低い送信方法であり、メッセージが本当に到達可能であることを保証することはできません。
1.2、ソースコード
/**
* {@link org.apache.rocketmq.client.producer.DefaultMQProducer}
*/
// 同步发送
public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {}
// 异步发送,sendCallback作为回调
public void send(Message msg,SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException {}
// 单向发送,不关心发送结果,最不靠谱
public void sendOneway(Message msg) throws MQClientException, RemotingException, InterruptedException {}
2.ソリューション2
2.1。説明
メッセージの送信が失敗またはタイムアウトした場合、メッセージは自動的に再試行します。デフォルトでは、3回再試行します。これは、APIに従って、たとえば10回に変更できます。
producer.setRetryTimesWhenSendFailed(10);
2.2、ソースコード
/**
* {@link org.apache.rocketmq.client.producer.DefaultMQProducer#sendDefaultImpl(Message, CommunicationMode, SendCallback, long)}
*/
// 自动重试次数,this.defaultMQProducer.getRetryTimesWhenSendFailed()默认为2,如果是同步发送,默认重试3次,否则重试1次
int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
int times = 0;
for (; times < timesTotal; times++) {
// 选择发送的消息queue
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
if (mqSelected != null) {
try {
// 真正的发送逻辑,sendKernelImpl。
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
switch (communicationMode) {
case ASYNC:
return null;
case ONEWAY:
return null;
case SYNC:
// 如果发送失败了,则continue,意味着还会再次进入for,继续重试发送
if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
continue;
}
}
// 发送成功的话,将发送结果返回给调用者
return sendResult;
default:
break;
}
} catch (RemotingException e) {
continue;
} catch (...) {
continue;
}
}
}
説明:
これは、コード全体ではなく、コア送信ロジックの要約です。それは次のように見ることができます:
- 同期の再試行回数は1+で
this.defaultMQProducer.getRetryTimesWhenSendFailed()
、他の方法のデフォルトは1 です。- this.defaultMQProducer.getRetryTimesWhenSendFailed()のデフォルトは2で、手動で設定できます
producer.setRetryTimesWhenSendFailed(10);
- sendKernelImplを呼び出して、実際にメッセージを送信します
- 同期して送信に失敗した場合は、続行します。つまり、もう一度入力して、送信を再試行します。
- 送信が成功すると、送信結果が発信者に返されます
- 例外が送信され、キャッチが入力された場合は、次回から再試行を続けてください。
3.ソリューション3
3.1。説明
ブローカーがダウンしているが、実稼働環境は一般にMとSを超えているため、サービスを提供し続ける他のマスターノードがあり、メッセージの送信に影響を与えることはなく、メッセージは引き続き到達可能であるとします。たとえば、ブローカーに送信された場合、ブローカーがダウンし、プロデューサーがブローカーからの応答を受信して送信に失敗するためです。このとき、プロデューサーは自動的に再試行し、ダウンしたブローカーはこの時点でオフラインでキックされたため、プロデューサーが変更されます。ブローカーはメッセージを送信します。
4.まとめ
プロデューサーは、送信フェーズ中にメッセージが到達可能であることをどのように確認しますか?
失敗した場合は、自動的に再試行します。N回再試行した後でも、クライアントはメッセージが失敗したことを認識します。これはそれ自体で補正することもでき、メインのビジネスロジックに盲目的に影響を与えることはありません。たとえば、ブローカーがダウンしている場合でも、サービスを再度提供する他のブローカーがあります。これは可用性が高く、影響を与えません。
簡単にまとめると、同期送信+自動再試行メカニズム+複数のマスターノード
3、ブローカー保管段階
ブローカーは最初にメッセージをメモリに入れ、次にフラッシュ戦略に従ってハードディスクに永続化する必要があります。メッセージはプロデューサーから受信されたばかりで、メモリにありますが、異常なダウンタイムによりメッセージが発生します。失われます。
1.ソリューション1
MQ永続性メッセージは、同期フラッシュと非同期フラッシュの2つのタイプに分けられます。デフォルトでは、ディスクを非同期で更新します。メッセージを受信した後、ブローカーは最初にディスクをキャッシュに保存し、すぐにメッセージを受信してストレージが成功したことをプロデューサーに通知します。その後、ビジネスロジックを続行できます。ブローカーは、永続化するために非同期でスレッドをセットアップしますブローカーがディスクに永続化される前にダウンした場合、メッセージは失われます。同期フラッシュとは、メッセージを受信してキャッシュに保存した後、メッセージが正常であることをプロデューサーに通知しませんが、メッセージがディスクに保持されるまでメッセージが終了したことをプロデューサーに通知しないことを意味します。これにより、メッセージが失われないことも保証されますが、パフォーマンスは非同期ほど高くありません。選択はビジネスシナリオによって異なります。
ブラッシング戦略を同期ブラッシングに変更します。デフォルトでは非同期フラッシュであり、構成は次のとおりです。
## 默认情况为 ASYNC_FLUSH,修改为同步刷盘:SYNC_FLUSH,实际场景看业务,同步刷盘效率肯定不如异步刷盘高。
flushDiskType = SYNC_FLUSH
対応するJava構成クラスは次のとおりです。
package org.apache.rocketmq.store.config;
public enum FlushDiskType {
// 同步刷盘
SYNC_FLUSH,
// 异步刷盘(默认)
ASYNC_FLUSH
}
非同期フラッシュはデフォルトで10秒に1回実行されます。ソースコードは次のとおりです。
/*
* {@link org.apache.rocketmq.store.CommitLog#run()}
*/
while (!this.isStopped()) {
try {
// 等待10s
this.waitForRunning(10);
// 刷盘
this.doCommit();
} catch (Exception e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
}
}
2.ソリューション2
クラスター展開、マスタースレーブモード、高可用性。
ブローカーが同期フラッシュ戦略を設定している場合でも、ブローカーがディスクをフラッシュした後にディスクが破損し、ディスク上のすべてのメッセージが失われます。しかし、それが1つのマスターと1つのスレーブであるが、フラッシュ後にマスターがスレーブと同期する時間がなかったとしても、ディスクは壊れています、それもGGではありませんか?そのとおり!
したがって、マスターがディスクのフラッシュを終了した後にプロデューサーに通知するだけでなく、マスターとスレーブがディスクのフラッシュを終了した後にプロデューサーに通知して、メッセージが正常であることをプロデューサーに通知するように構成することもできます。
## 默认为 ASYNC_MASTER
brokerRole=SYNC_MASTER
3.まとめ
ブローカーのメッセージストレージフェーズ中にメッセージが失われないように厳密に確認する場合は、次の構成が必要ですが、パフォーマンスはデフォルト構成よりも明らかにはるかに劣ります。
# master 节点配置
flushDiskType = SYNC_FLUSH
brokerRole=SYNC_MASTER
# slave 节点配置
brokerRole=slave
flushDiskType = SYNC_FLUSH
上記の構成の意味は次のとおりです。
プロデューサーがブローカーにメッセージを送信した後、ブローカーのマスターノードは最初にディスクに永続化し、次にデータをスレーブノードに同期します。スレーブノードが同期されてディスクが配置された後、プロデューサーに戻って次のように言います。メッセージは大丈夫だと。
第四に、消費者消費段階
消費の失敗は、実際にはメッセージ損失の一種です。
1.ソリューション1
コンシューマーは最初にメッセージをローカルにプルし、次にビジネスロジックを実行し、ビジネスロジックが完了した後、手動でackを確認します。そうして初めて、それは本当に消費の完了を表します。メッセージはローカルにプルされた後に消費されると言うのではなく。例えば
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : msgs) {
String str = new String(msg.getBody());
System.out.println(str);
}
// ack,只有等上面一系列逻辑都处理完后,到这步CONSUME_SUCCESS才会通知broker说消息消费完成,如果上面发生异常没有走到这步ack,则消息还是未消费状态。而不是像比如redis的blpop,弹出一个数据后数据就从redis里消失了,并没有等我们业务逻辑执行完才弹出。
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
2.ソリューション2
メッセージ消費の失敗は自動的に再試行します。確認メッセージなしで消費メッセージが失敗した場合、自動的に再試行されます。再試行の戦略と時間(デフォルトは15回)は次のように構成されます。
/**
* Broker可以配置的所有选项
*/
public class org.apache.rocketmq.store.config.MessageStoreConfig {
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
}