如果要保证顺序消费,那么他的核心点就是:生产者有序存储、消费者有序消费
实现原理
这里有一点很重要的是:同一个queue,存储在里面的message 是按照先进先出的原则
OK,我们继续接着之前的demo springboot集成RocketMq
-
生产端:我们知道生产的message最终会存放在Queue中,生产者发送消息的思路就是:在一个订单操作的过程中,订单产生,订单支付,订单生成,产生的这四个消息,都发送到同一个Queue队列中,那么取消息的时候就可以保证先进先取出。
试想,如果要是不发送同一个Queue中,发送到一个topic下的所有队列中,能保证顺序吗?肯定不能,因为在一个消费者集群的情况下,消费者1先去Queue拿消息,它拿到了 订单生成,它拿完后,消费者2去queue拿到的是 订单支付。拿的顺序是没毛病了,但关键是先拿到不代表先消费完它。会存在虽然你消费者1先拿到订单生成,但由于网络等原因,消费者2比你真正的先消费消息。这是不是很尴尬了。
-
消费端:我们采用锁,锁住一个Queue,当线程1来取订单消息的时候,取到订单完成,然后给这个队列加一把锁,然后等线程1消费完订单完成这个消息之后,在去取下一个消息订单支付。这样的一个过程来真正保证对同一个Queue能够真正意义上的顺序消费,而不仅仅是顺序取出。
注意事项:
1、顺序消息暂不支持广播模式。
2、顺序消息不支持异步发送方式,否则将无法严格保证顺序。
3、建议同一个 Group ID 只对应一种类型的 Topic,即不同时用于顺序消息和无序消息的收发。
4、对于全局顺序消息,建议创建实例个数 >=2。
代码实现
- 生产端 同一orderID的订单放到同一个queue。
- 消费端 同一个queue取出消息的时候锁住整个queue,直到消费后再解锁。
MQ消息投递机制,我们可以用SelectMessageQueueByHash算法
代码实现:
@RequestMapping("/test/sendRocketMq")
@ResponseBody
public String sendRocketMq(){
List<Order> orderNoList = new ArrayList<Order>();
orderNoList.add(new Order("DD0000003","订单创建"));
orderNoList.add(new Order("DD0000003","订单支付"));
orderNoList.add(new Order("DD0000004","订单创建"));
orderNoList.add(new Order("DD0000003","订单完成"));
orderNoList.add(new Order("DD0000005","订单支付"));
orderNoList.add(new Order("DD0000006","订单创建"));
orderNoList.add(new Order("DD0000004","订单支付"));
orderNoList.add(new Order("DD0000005","订单创建"));
orderNoList.add(new Order("DD0000006","订单支付"));
orderNoList.add(new Order("DD0000006","订单完成"));
orderNoList.add(new Order("DD0000005","订单完成"));
orderNoList.add(new Order("DD0000004","订单完成"));
for (int i = 0; i < orderNoList.size(); i++) {
SendResult sendResult = sendMsgUtil.sendMsg(orderNoList.get(i), MsgProductBean.ACCOUNT_INFO);
}
return "success";
}
MQ工具类修改:
public SendResult sendMsg(Order order, MsgProductBean object) {
Message message = new Message(topic, object.getTagName(), (order.getOrderNo() + " : " + order.getOrderStatus()).getBytes());
try {
// SendResult sendResult = defaultMQProducer.send(message);
SendResult sendResult = defaultMQProducer.send(message, new SelectMessageQueueByHash(), order.getOrderNo());
logger.info("Product:发送状态={}, 存储queue={}, 订单order={} , 订单状态status={}", sendResult.getSendStatus(),
sendResult.getMessageQueue().getQueueId(), order.getOrderNo(), order.getOrderStatus());
return null;
} catch (MQClientException e) {
e.printStackTrace();
} catch (RemotingException e) {
e.printStackTrace();
} catch (MQBrokerException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
结果:
接下里我们来说消费端:
这里主要用到的是这个类:MessageListenerOrderly
@Component
//public class MsgListener implements MessageListenerConcurrently {
public class MsgListener implements MessageListenerOrderly {
final static Logger logger = LoggerFactory.getLogger(MsgListener.class);
@Autowired
private MsgCustomerService msgCustomerService;
int i = 0;
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
msgs.forEach(messageExt -> {
msgCustomerService.handlerMsg(messageExt);
});
i ++ ;
logger.info("i = " + i);
return ConsumeOrderlyStatus.SUCCESS;
}
// @Override
// public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
// msgs.forEach(messageExt -> {
// msgCustomerService.handlerMsg(messageExt);
// });
// i ++ ;
// logger.info("i = " + i);
// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
// }
}
结果如下:
OK,这样就实现了我们的顺序消费。