顺序消息是指消息的消费顺序和产生顺序相同,在有些业务逻辑下,必须保证顺序。比如订单的生成、付款、发货,这3个消息必须按顺序处理才行。顺序消息分为全局顺序消息和部分顺序消息,全局顺序消息指某个Topic下的所有消息都要保证顺序;部分顺序消息只要保证每一组消息被顺序消费即可,比如上面订单消息的例子,只要保证同一个订单ID的三个消息能按顺序消费即可。
1.全局顺序消息
RocketMQ在默认情况下不保证顺序,比如创建一个Topic,默认八个写队列,八个读队列。这时候一条消息可能被写入任意一个队列里;在数据的读取过程中,可能有多个Consumer,每个Consumer也可能启动多个线程并行处理,所以消息被哪个Consumer消费,被消费的顺序和写入的顺序是否一致是不确定的。
要保证全局顺序消息,需要先把Topic的读写队列数设置为一,然后Producer和Consumer的并发设置也要是一。简单来说,为了保证整个Topic的全局消息有序,只能消除所有的并发处理,各部分都设置成单线程处理。这时高并发、高吞吐量的功能完全用不上了。
在实际应用中,更多的是像订单类消息那样,只需要部分有序即可。在这种情况下,我们经过合适的配置,依然可以利用RocketMQ高并发、高吞吐量的能力。
2.部分顺序消息
要保证部分消息有序,需要发送端和消费端配合处理。在发送端,要做到把同一业务ID的消息发送到同一个Message Queue;在消费过程中,要做到从同一个Message Queue读取的消息不被并发处理,这样才能达到部分有序。
发送端使用MessageQueueSelector类来控制把消息发往哪个Message Queue,如代码清单6-1所示。
代码清单6-1 MessageQueueSelector示例
for (int i = 0; i < 100; i++) {
int orderId = i;
//Create a message instance, specifying topic, tag and message body.
Message msg = new Message("OrderTopic8", tags, "KEY" + i,
("Hello RocketMQ " +orderId+" "+ i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = Producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
System.out.println("queue selector mq nums:"+mqs.size());
System.out.println("msg info:"+msg.toString());
for(MessageQueue mq: mqs){
System.out.println(mq.toString());
}
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
System.out.println(sendResult);
}
消费端通过使用MessageListenerOrderly类来解决单Message Queue的消息被并发处理的问题,如代码清单6-2所示。
代码清单6-2 MessageListenerOrderly示例
consumer.registerMessageListener(new MessageListenerOrderly() {
AtomicLong consumeTimes = new AtomicLong(0);
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeOrderlyContext context) {
System.out.printf(" Received New Messages: " + new String(msgs.get(0).getBody()) + "%n");
return ConsumeOrderlyStatus.SUCCESS;
}
});
Consumer使用MessageListenerOrderly的时候,下面四个Consumer的设置依旧可以使用:setConsumeThreadMin、setConsumeThreadMax、setPull-BatchSize、setConsumeMessageBatchMaxSize。前两个参数设置Consumer的线程数,PullBatchSize指的是一次从Broker的一个Message Queue获取消息的最大数量,默认值是32,ConsumeMessageBatchMaxSize指的是这个Consumer的Executor(也就是调用MessageListener处理的地方)一次传入的消息数(List<MessageExt>msgs这个链表的最大长度),默认值是1。上述四个参数可以使用,说明MessageListenerOrderly并不是简单地禁止并发处理。在MessageListenerOrderly的实现中,为每个Consumer Queue加个锁,消费每个消息前,需要先获得这个消息对应的Consumer Queue所对应的锁,这样保证了同一时间,同一个Consumer Queue的消息不被并发消费,但不同Consumer Queue的消息可以并发处理。