应用场景:
网购的时候,我们需要下单,那么下单需要假如有三个顺序,第一、创建订单 ,第二:订单付款,第三:订单完成。也就是这个三个环节要有顺序,这个订单才有意义。RocketMQ可以保证顺序消费
实现原理:
produce在发送消息的时候,把消息发到同一个队列(queue)中。发送消息的时候可以实现MessageQueueSelector类的select 方法,返回的就是queue。
消费者注册消息监听器为MessageListenerOrderly,这个监听器是让消费者独占的,这样就可以保证消费端只有一个线程去消费消息。
代码:
public static void main(String[] args) throws Exception {
//Instantiate with a producer group name.
DefaultMQProducer producer = new DefaultMQProducer(Configure.BROKER_GROUP);
producer.setNamesrvAddr(Configure.NAMESRV_ADDR);
//Launch the instance.
producer.start();
String[] tags = new String[] {Configure.MESSAGE_TAG_ORDER_1,Configure.MESSAGE_TAG_ORDER_2};
for (int i = 0; i < 100; i++) {
int orderId = i % 10;
log.info("orderId{}",orderId);
//创建消息
Message msg = new Message(Configure.MESSAGE_TOPIC, tags[i % tags.length], "KEY" + i,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
//发送消息,重写选择MessageQueue 方法,把消息写到对应的ConsumerQueue 中
// orderId 参数传递到内部方法 select arg 参数
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
log.info("arg{}",arg);
Integer id = (Integer) arg;
int index = id % mqs.size();
//返回选中的队列
return mqs.get(index);
}
}, orderId);
System.out.printf("%s%n", sendResult);
}
//server shutdown
producer.shutdown();
}
消费者:
public static void main(String[] args) throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(Configure.CONSUMER_GROUP);
consumer.setNamesrvAddr(Configure.NAMESRV_ADDR);
consumer.subscribe(Configure.MESSAGE_TOPIC, Configure.MESSAGE_TAG_ORDER_1+" || "+Configure.MESSAGE_TAG_ORDER_2);
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeOrderlyContext context) {
//手动确认
context.setAutoCommit(false);
msgs.forEach(m->{
System.out.print("host:"+m.getBornHost()+"--");
System.out.print("key:"+m.getKeys()+"--");
System.out.print("Topic:"+m.getTopic()+"--");
System.out.print("QueueId:"+m.getQueueId()+"--");
System.out.print("tags:"+m.getTags()+"--");
System.out.print("msg:"+new String(m.getBody()));
System.out.println();
});
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
log.info("Consumer Started.%n");
}
注:代码复制多份,启用使用多个消费者(容易看日志)。
测试结果(启用了三个消费者):
消费者1,消费日志
消费者2,消费日志
消费者3,消费日志
测试总结:
消费者1,消费了queue0,和queue1,通过msg tag 和queue id,可以确定在一个队列上的msg tag,消息是被顺序消费的。
消费者2和消费者3分别消费queue3和queue2 上的消息,很容易看出来是顺序消费的。也可以把消息只发送到一个队列,进行测试。
实现:
1、发送消息时把业务相关的消息发送到同一个consumer queue
2、消费消息时使用 MessageListenerOrderly,消费消息时要获取consumer queue的锁后才能消费消息(消费消息内部使用的是线程池来消费的)。