1.説明
2つのタイプがあります。1つはメッセージを直接送信することです。クライアント内のキューを選択するためのアルゴリズムがあり、外部からの変更は許可されていません。カスタムキュー選択アルゴリズムもあります(3つのアルゴリズムが組み込まれています。気に入らない場合は、アルゴリズムをカスタマイズして実現できます)。
public class org.apache.rocketmq.client.producer.DefaultMQProducer {
// 只发送消息,queue的选择由默认的算法来实现
@Override
public SendResult send(Collection<Message> msgs) {}
// 自定义选择queue的算法进行发消息
@Override
public SendResult send(Collection<Message> msgs, MessageQueue messageQueue) {}
}
第二に、ソースコード
1、send(msg、mq)
1.1、使用シナリオ
デフォルトのキュー選択アルゴリズムが必要ない場合もありますが、カスタマイズする必要があります。一般的に、最も一般的に使用されるシナリオはシーケンシャルメッセージです。シーケンシャルメッセージの送信では、通常、特定の特性セットを持つメッセージが同じキューで送信されることを指定します。順序が保証されるように。、単一のキューが順序付けられているため。
シーケンスメッセージがわからない場合は、前回のシーケンスメッセージの記事をご覧ください。
1.2、主成分分析
3つの組み込みアルゴリズムがあり、それらはすべて共通のインターフェースを実装しています。
org.apache.rocketmq.client.producer.MessageQueueSelector
-
SelectMessageQueueByRandom
-
SelectMessageQueueByHash
-
SelectMessageQueueByMachineRoom
-
ロジックをカスタマイズする場合は、インターフェイスを直接実装して、selectメソッドをオーバーライドできます。
非常に典型的な戦略モード、異なるアルゴリズムには異なる実装クラスがあり、トップレベルのインターフェースがあります。
1.2.1、SelectMessageQueueByRandom
public class SelectMessageQueueByRandom implements MessageQueueSelector {
private Random random = new Random(System.currentTimeMillis());
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
// mqs.size():队列的个数。假设队列个数是4,那么这个value就是0-3之间随机。
int value = random.nextInt(mqs.size());
return mqs.get(value);
}
}
とても簡単なのは純粋なランダム性です。
mqs.size():キューの数。キューの数が4であると仮定すると、この値は0〜3の間でランダムになります。
1.2.2、SelectMessageQueueByHash
public class SelectMessageQueueByHash implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = arg.hashCode();
// 防止出现负数,取个绝对值,这也是我们平时开发中需要注意到的点
if (value < 0) {
value = Math.abs(value);
}
// 直接取余队列个数。
value = value % mqs.size();
return mqs.get(value);
}
}
とても簡単で、純粋に残りを取ります。
mqs.size():キューの数。キューの数が4で、値のハッシュコードが3、3%4 = 3であるとすると、添え字は0から始まるため、これが最後のキュー、つまり4番目のキューになります。
1.2.3、SelectMessageQueueByMachineRoom
public class SelectMessageQueueByMachineRoom implements MessageQueueSelector {
private Set<String> consumeridcs;
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return null;
}
public Set<String> getConsumeridcs() {
return consumeridcs;
}
public void setConsumeridcs(Set<String> consumeridcs) {
this.consumeridcs = consumeridcs;
}
}
鳥の用途がわからないので、nullを返すだけです。したがって、カスタム要件がある場合は、直接カスタマイズするだけで、卵の用途はわかりません。
1.2.4、カスタムアルゴリズム
public class MySelectMessageQueue implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return mqs.get(0);
}
}
最初のキューであるキュー0を常に選択してください。例を挙げると、それは本当にあなたのビジネスニーズに依存します。
1.3、コールチェーン
org.apache.rocketmq.client.producer.DefaultMQProducer#send(Message msg, MessageQueueSelector selector, Object arg)
->
org.apache.rocketmq.client.producer.DefaultMQProducer#send(Message msg, MessageQueueSelector selector, Object arg)
->
org.apache.rocketmq.client.producer.DefaultMQProducer#send(Message msg, MessageQueueSelector selector, Object arg, long timeout)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendSelectImpl(xxx)
->
mq = mQClientFactory.getClientConfig().queueWithNamespace(selector.select(messageQueueList, userMessage, arg));
->
selector.select(messageQueueList, userMessage, arg)
->
org.apache.rocketmq.client.producer.MessageQueueSelector#select(final List<MessageQueue> mqs, final Message msg, final Object arg)
2、send(msg)
2.1、使用シナリオ
通常、これは特別な要件がないシナリオで使用されます。彼のデフォルトのキュー選択アルゴリズムは非常に優れているため、さまざまな最適化シナリオが考えられています。
2.2。主成分分析
// {@link org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl}
// 这是发送消息核心原理,不清楚的看我之前发消息源码分析的文章
// 选择消息要发送的队列
MessageQueue mq = null;
for (int times = 0; times < 3; times++) {
// 首次肯定是null
String lastBrokerName = null == mq ? null : mq.getBrokerName();
// 调用下面的方法进行选择queue
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
if (mqSelected != null) {
// 给mq赋值,如果首次失败了,那么下次重试的时候(也就是下次for的时候),mq就有值了。
mq = mqSelected;
......
// 很关键,能解答下面会提到的两个问题:
// 1.faultItemTable是什么时候放进去的?
// 2.isAvailable() 为什么只是判断一个时间就可以知道Broker是否可用?
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
}
}
キューの正面玄関を選択します
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
// 默认为false,代表不启用broker故障延迟
if (this.sendLatencyFaultEnable) {
try {
// 随机数且+1
int index = tpInfo.getSendWhichQueue().getAndIncrement();
// 遍历
for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
// 先(随机数 +1) % queue.size()
int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
if (pos < 0) {
pos = 0;
}
MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
// 看找到的这个queue所属的broker是不是可用的
if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
// 非失败重试,直接返回到的队列
// 失败重试的情况,如果和选择的队列是上次重试是一样的,则返回
// 也就是说如果你这个queue所在的broker可用,
// 且不是重试进来的或失败重试的情况,如果和选择的队列是上次重试是一样的,那你就是天选之子了。
if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
}
// 如果所有队列都不可用,那么选择一个相对好的broker,不考虑可用性的消息队列
final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
if (writeQueueNums > 0) {
final MessageQueue mq = tpInfo.selectOneMessageQueue();
if (notBestBroker != null) {
mq.setBrokerName(notBestBroker);
mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
}
return mq;
} else {
latencyFaultTolerance.remove(notBestBroker);
}
} catch (Exception e) {
log.error("Error occurred when selecting message queue", e);
}
// 随机选择一个queue
return tpInfo.selectOneMessageQueue();
}
// 当sendLatencyFaultEnable=false的时候选择queue的方法,默认就是false。
return tpInfo.selectOneMessageQueue(lastBrokerName);
}
2.2.1。ブローカー障害遅延を有効にしないでください
sendLatencyFaultEnableはデフォルトでfalseであるため、sendLatencyFaultEnable = falseの場合は最初にロジックを確認してください
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
// 第一次就是null,第二次(也就是重试的时候)就不是null了。
if (lastBrokerName == null) {
// 第一次选择队列的逻辑
return selectOneMessageQueue();
} else {
// 第一次选择队列发送消息失败了,第二次重试的时候选择队列的逻辑
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
int pos = Math.abs(index++) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
MessageQueue mq = this.messageQueueList.get(pos);
// 过滤掉上次发送消息失败的队列
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
return selectOneMessageQueue();
}
}
次に、キューを初めて選択するロジックを引き続き確認します。
public MessageQueue selectOneMessageQueue() {
// 当前线程有个ThreadLocal变量,存放了一个随机数 {@link org.apache.rocketmq.client.common.ThreadLocalIndex#getAndIncrement}
// 然后取出随机数根据队列长度取模且将随机数+1
int index = this.sendWhichQueue.getAndIncrement();
int pos = Math.abs(index) % this.messageQueueList.size();
if (pos < 0) {
pos = 0;
}
return this.messageQueueList.get(pos);
}
まあ、それは実際には少しランダムを意味します。ただし、ハイライトは、キューの長さを法として乱数を取り出し、乱数+1を追加すると、この+1が点灯することです(getAndIncrement cas +1)。
メッセージの送信に初めて失敗した場合、lastBrokerNameは、現在の選択に失敗したブローカーを格納します(mq = mqSelected)。再試行後、lastBrokerNameには値があります。これは、最後に選択されたボーカーが送信に失敗したことを意味します。 sendWhichQueueローカルスレッド変数+1。最後のブローカーでなくなるまで選択メッセージキューをトラバースします。これは、最後に送信に失敗したブローカーのロジックを回避するためです。
例:今回の乱数は1で、キューの長さは4、1%4 = 1であり、この時点で失敗して再試行に入ります。その後、再試行の前、つまり前のステップ1%4の後、彼は1を置きます++操作が実行された後、それは2になるので、今回再試行すると、2%4 = 2になり、失敗したばかりのブローカーを直接除外します。
次に、2番目の再試行選択キューのロジックを引き続き確認します。
// +1
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
// 取模
int pos = Math.abs(index++) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
MessageQueue mq = this.messageQueueList.get(pos);
// 过滤掉上次发送消息失败的队列
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
// 没找到能用的queue的话继续走默认的那个
return selectOneMessageQueue();
とても簡単で、前回失敗しませんでした、私に入ってからもう一度やり直しましたか?私も非常に単純です。乱数+1を取り出して、キューの長さを調整します。ブローカーが前回失敗したかどうかを確認します。ブローカーが彼の子供である場合は、フィルターで除外し、キューをトラバースして検索します。使用できる次のもの。
2.2.2、ブローカー障害遅延を有効にする
それは次の場合のロジックです
if (this.sendLatencyFaultEnable) {
....
}
上記のコメントを見てください。非常に明確です。最初に(随机数 +1) % queue.size()
、キューが属するブローカーが使用可能かどうかを確認します。使用可能な場合、再試行または失敗しません。選択したキューが最後の場合再試行テストは同じです。その後、戻るだけで完了です。では、ブローカーが利用可能かどうかをどのように確認しますか?
// {@link org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl#isAvailable(String)}
public boolean isAvailable(final String name) {
final FaultItem faultItem = this.faultItemTable.get(name);
if (faultItem != null) {
return faultItem.isAvailable();
}
return true;
}
// {@link org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl.FaultItem#isAvailable()}
public boolean isAvailable() {
return (System.currentTimeMillis() - startTimestamp) >= 0;
}
疑問に思う:
faultItemTableはいつ入れられましたか?
isAvailable()一定期間の判断だけでブローカーが利用可能かどうかを知ることができるのはなぜですか?
これには、メッセージが上記で送信された後に呼び出されるメソッドが必要です。
// {@link org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#updateFaultItem}
// 发送开始时间
beginTimestampPrev = System.currentTimeMillis();
// 进行发送
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);
// 发送结束时间
endTimestamp = System.currentTimeMillis();
// 更新broker的延迟情况
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
詳細なロジックは次のとおりです。
// {@link org.apache.rocketmq.client.latency.MQFaultStrategy#updateFaultItem}
public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
if (this.sendLatencyFaultEnable) {
// 首次isolation传入的是false,currentLatency是发送消息所耗费的时间,如下
// this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
}
}
private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
// 根据延迟时间对比MQFaultStrategy中的延迟级别数组latencyMax 不可用时长数组notAvailableDuration 来将该broker加进faultItemTable中。
private long computeNotAvailableDuration(final long currentLatency) {
for (int i = latencyMax.length - 1; i >= 0; i--) {
// 假设currentLatency花费了10ms,那么latencyMax里的数据显然不符合下面的所有判断,所以直接return 0;
if (currentLatency >= latencyMax[i])
return this.notAvailableDuration[i];
}
return 0;
}
// {@link org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl#updateFaultItem()}
@Override
// 其实主要就是给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
// 也就是说只有notAvailableDuration == 0的时候,isAvailable()才会返回true。
public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
FaultItem old = this.faultItemTable.get(name);
if (null == old) {
final FaultItem faultItem = new FaultItem(name);
faultItem.setCurrentLatency(currentLatency);
// 给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
old = this.faultItemTable.putIfAbsent(name, faultItem);
if (old != null) {
old.setCurrentLatency(currentLatency);
// 给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
}
} else {
old.setCurrentLatency(currentLatency);
// 给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
}
}
次の2行のコードについて詳しく説明します。
private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
レイテンシーマックス | notAvailableDuration |
---|---|
50L | 0L |
100L | 0L |
550L | 30000L |
1000L | 60000L |
2000L | 120000L |
3000L | 180000L |
15000L | 600000L |
これは
-
currentLatencyが50以上100未満の場合、notAvailableDurationは0です。
-
currentLatencyが100以上550未満の場合、notAvailableDurationは0です。
-
currentLatencyが550以上1000未満の場合、notAvailableDurationは300000です。
-
…などなど
別の例を挙げましょう:
分離がtrueに渡されると仮定すると、
long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
次に、notAvailableDurationが600000Lで渡されます。isAvailableメソッドと組み合わせると、おおよそのプロセスは次のようになります。
RocketMQは、各ブローカーの利用可能時間(現在の時間+ notAvailableDuration)を予測します。現在の時間がこの時間よりも大きい場合、ブローカーが利用可能であることを意味し、notAvailableDurationにはlatencyMaxの間隔に対応する6つのレベルがあります。 、および着信currentLatencyに従って予測します。ブローカーはいつ利用可能になりますか。
だからこれをもう一度見てください
public boolean isAvailable() {
return (System.currentTimeMillis() - startTimestamp) >= 0;
}
どの間隔に該当するかを確認する実行時間に応じて、0〜100の時間内にnotAvailableDurationが0になり、すべて利用可能になります。この値を超えると、利用可能時間が増加し始め、考慮されます。最適な解決策ではありません。あきらめてください。
2.3、コールチェーン
org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send(org.apache.rocketmq.common.message.Message)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send(org.apache.rocketmq.common.message.Message, long)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl(xxx)
->
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#selectOneMessageQueue(xxx)
org.apache.rocketmq.client.latency.MQFaultStrategy#selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName)
2.4。まとめ
-
フォールトトレランスが有効になっていない場合は、キューをポーリングして送信します。失敗した場合は、再試行時に失敗したブローカーをフィルタリングします。
-
フォールトトレランス戦略が有効になっている場合、RocketMQの予測メカニズムを使用して、ブローカーが利用可能かどうかを予測します。
-
前回失敗したブローカーが利用可能な場合、ブローカーのキューは引き続き選択されます
-
上記の状況が失敗した場合は、送信するものをランダムに選択してください
-
メッセージを送信するとき、それは呼び出しの時間とエラーが報告されるかどうかを記録し、時間に基づいてブローカーの利用可能な時間を予測します
3、まとめ
1.質問
彼には2つのオーバーロードされたsend()メソッドがあり、1つはアルゴリズムセレクターをサポートし、もう1つはアルゴリズム選択をサポートしていません。キューアルゴリズム選択は典型的な戦略モードです。なぜsend(message)
別々のクラスに抽出していない内蔵のキュー選択アルゴリズムがあり、その後、このクラスを実装org.apache.rocketmq.client.producer.MessageQueueSelector
インタフェースを?たとえばSelectMessageQueueByBest
、次のように呼ばれます。
public class org.apache.rocketmq.client.producer.DefaultMQProducer {
// 只发送消息,queue的选择由默认的算法来实现
@Override
public SendResult send(Collection<Message> msgs) {
this.send(msgs, new SelectMessageQueueByBest().select(xxx));
}
// 自定义选择queue的算法进行发消息
@Override
public SendResult send(Collection<Message> msgs, MessageQueue messageQueue) {}
}
私の推測では、このアルゴリズムは複雑すぎて、他のタイプとの相互作用が多すぎて、パラメーターが他の3つの組み込みのものと異なる可能性があるため、それらをまとめることはできませんでしたが、それでも一緒に標準化されました。彼らは同じことをしましたが、アルゴリズムは異なり、それは典型的な戦略モードです。
2.インタビュー
Q:メッセージを送信するときにキューを選択するためのアルゴリズムは何ですか?
回答:メッセージを直接送信するタイプとキューを選択できないタイプの2つがあります。キュー選択アルゴリズムは次のとおりです。
-
フォールトトレランスが有効になっていない場合は、キューをポーリングして送信します。失敗した場合は、再試行時に失敗したブローカーをフィルタリングします。
-
フォールトトレランス戦略が有効になっている場合、RocketMQの予測メカニズムを使用して、ブローカーが利用可能かどうかを予測します。
-
前回失敗したブローカーが利用可能な場合、ブローカーのキューは引き続き選択されます
-
上記の状況が失敗した場合は、送信するものをランダムに選択してください
-
メッセージを送信するとき、それは呼び出しの時間とエラーが報告されるかどうかを記録し、時間に基づいてブローカーの利用可能な時間を予測します
もう1つは、メッセージを送信するときにアルゴリズムを選択し、インターフェイスのカスタムアルゴリズムを実装することです。
-
SelectMessageQueueByRandom
:ランダム -
SelectMessageQueueByHash
:ハッシュ -
SelectMessageQueueByMachineRoom
-
インターフェイスのカスタマイズを実装する