1つは、シーケンスメッセージの解釈
1。概要
RocketMQメッセージはトピックキューに格納され、キュー自体はFIFO(First Int First Out)キューです。したがって、単一のキューで順序を保証できます。ただし、問題は、トピックにN個のキューがあることです。作成者の設計の利点も明らかです。クラスタリングと負荷分散の特性を自然にサポートし、各キューに大量のデータを均等に分散します。10個のメッセージをに送信します。同じトピック。、これらの10個のメッセージは、トピックの下のすべてのキューに自動的に分散されるため、消費するときに、どのキューが最初に消費され、どのキューが後で消費されるかは必ずしも必要ではなく、無秩序な消費につながります。
2.グラフィック
3.再度分析します
プロデューサーは、4つのメッセージm1、m2、m3、およびm4をトピックに送信します。トピックには4つのキューがあります。独自の負荷分散戦略により、4つのキューのそれぞれに1つのメッセージが格納されます。キュー1に格納されたm1、キュー2に格納されたm2、キュー3に格納されたm3、およびキュー4に格納されたm4。コンシューマはマルチスレッド消費を消費するため、送信の順序など、どのキューまたはメッセージが最初に消費されるかを保証できません。 m1、m2、m3、m4ですが、コンシューマーはコンシューマー内の複数のスレッドによって消費されるため、最初にqueue4キューのm4を消費し、次にm1を消費する可能性があります。これは無秩序につながります。
二、解決策
1.オプション1
簡単に言うと、問題の鍵は、複数のキューにメッセージがあることです。消費すると、どのキューのメッセージが最新かわかりません。メッセージを送信するときに秩序を確保したい場合は、メッセージをキューに送信してから、消費するときに送信します。そのキューだけにメッセージがあり、キューはFIFOであるため、先入れ先出しなので、通常の消費です。終わった。非常に完璧な。また、RocketMQは、メッセージの送信時にキューを選択するためのAPI(MessageQueueSelector)も提供します。コードに直接移動します。
2.コード1
2.1。プロデューサー
import java.util.List;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
/**
* 消息发送者
*/
public class Producer5 {
public static void main(String[] args)throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("my-order-producer");
producer.setNamesrvAddr("124.57.180.156:9876");
producer.start();
for (int i = 0; i < 5; i++) {
Message message = new Message("orderTopic", ("hello!" + i).getBytes());
producer.send(
// 要发的那条消息
message,
// queue 选择器 ,向 topic中的哪个queue去写消息
new MessageQueueSelector() {
// 手动 选择一个queue
@Override
public MessageQueue select(
// 当前topic 里面包含的所有queue
List<MessageQueue> mqs,
// 具体要发的那条消息
Message msg,
// 对应到 send() 里的 args,也就是2000前面的那个0
// 实际业务中可以把0换成实际业务系统的主键,比如订单号啥的,然后这里做hash进行选择queue等。能做的事情很多,我这里做演示就用第一个queue,所以不用arg。
Object arg) {
// 向固定的一个queue里写消息,比如这里就是向第一个queue里写消息
MessageQueue queue = mqs.get(0);
// 选好的queue
return queue;
}
},
// 自定义参数:0
// 2000代表2000毫秒超时时间
0, 2000);
}
}
}
2.2。消費者
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* Description:
*
* @author TongWei.Chen 2020-06-22 11:17:47
*/
public class ConsumerOrder {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("my-consumer");
consumer.setNamesrvAddr("124.57.180.156:9876");
consumer.subscribe("orderTopic", "*");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println(new String(msg.getBody()) + " Thread:" + Thread.currentThread().getName() + " queueid:" + msg.getQueueId());
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("Consumer start...");
}
}
2.3、出力結果
Consumer start...
hello!0 Thread:ConsumeMessageThread_1 queueid:0
hello!1 Thread:ConsumeMessageThread_1 queueid:0
hello!2 Thread:ConsumeMessageThread_1 queueid:0
hello!3 Thread:ConsumeMessageThread_1 queueid:0
hello!4 Thread:ConsumeMessageThread_1 queueid:0
非常に完璧で整然とした出力!
3.状況2
たとえば、新しい要件:すべての未払いの注文をqueue1に入れ、有料の注文をqueue2に入れ、異常な支払いの注文をqueue3に入れます。次に、消費するときに各キューが正しいことを確認する必要があります。、queue1を消費できず、直接queue2。キューを1つずつ消費する必要があります。
現時点では、メッセージを送信するときにカスタムパラメータargを使用することをお勧めします。メッセージ本文には支払いステータスが含まれている必要があります。未払いと判断された場合は、queue1などを選択します。これにより、各キューに同じ状態のメッセージのみが含まれるようになります。そのため、現在、コンシューマーは複数のスレッドによって消費されていますが、これは故障している必要があります。3つのキューがランダムに消費されます。解決策はより単純で、コンシューマー側のスレッド数を直接1に変更して、キューがFIFOになり、コンシューマー側が1つずつ消費するようにします。RocketMQは、次の2つの文でそのようなAPIも提供します。
// 最大线程数1
consumer.setConsumeThreadMax(1);
// 最小线程数
consumer.setConsumeThreadMin(1);