RocketMq如何实现消息有序性

934 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

背景

商城系统,一般订单的完成都必须需经过三个有序的步骤:订单创建、订单付款、订单生成。系统需产生了订单创建、订单付款、订单生成产生三条消息,消息消费需要按照上述顺序消费消息才能保证业务的正确性。

消息有序性分类

RocketMQ能严格保证消息的有序性,将顺序消息分为全局顺序消息与分区顺序消息。

  • 全局顺序:对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。
  • 分区顺序:对于指定的一个 Topic,所有消息根据hashKey进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。

RocketMQ的一个topic默认有4个消息队列,普通消息的发送,是通过轮询的方式选择队列,然后将消息发送出去的,消息之间是没有顺序的。要保证RocketMQ消息全局有序,那么一个topic有且只能一个消息队列,所有的消息都是严格按照先进先出的顺序进行发布和消费。要保证分区有序,将消息根据hashKey进行区分,然后将同一个分区的消息严格按照先进先出的顺序进行发布和消息,也就说将同一分区的消息发送到一个队列中去。

需要注意:发送全局顺序消息,性能比较差,只能用一个队列进行消息的发送。大多数场景都是保证消息分区顺序就行了。

如何保证消息的有序性

RocketMQ实现消息的有序性,需要从三个方面一起保证消息的有序性:

  • 生产者生产顺序消息
  • Broker保存顺序消息
  • 消费者顺序消费消息

生产者生产顺序消息

Producer端确保消息顺序唯一要做的事情就是将消息路由到特定的分区,在RocketMQ中,通过MessageQueueSelector来实现分区的选择,具体的实现如下:

图片.png

Broker保存顺序消息

只要生产者发送过来的消息是有序的,Broker按照生产者发送过来的消息保存起来就保证消息在Broker服务器的有序性,当消息保存成功以后,才给生产者发送ack确认,生产者才会发送下一条消息。即使消息重复发送,也是没有关系的,只要在消费者端做幂等,将重复的消息去重就行了。

消费者顺序消费消息

RocketMQ消费端有两种类型:MQPullConsumer和MQPushConsumer。

  • MQPullConsumer由用户控制线程,主动从服务端获取消息,每次获取到的是一个MessageQueue中的消息。PullResult中的List和存储顺序一致,只需要自己保证消费的顺序。

  • MQPushConsumer由用户注册MessageListener来消费消息,在客户端中需要保证调用MessageListener时消息的顺序性。

实现流程如下:

图片.png

具体实现代码

生成者发送顺序消息

@Component
public class OrderProduce
{
    private Logger logger = LoggerFactory.getLogger(getClass());
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    public void sendOrderMessage(String hashKey)
    {
        logger.info("start sendOrderMessage hashKey:{}",hashKey);
        String message="mesageid";
        for(int i=0;i<5;i++)
        {
            rocketMQTemplate.syncSendOrderly("order-topic", message+i,hashKey);
        }
      
    } 
}

说明:hashKey 通常可以采用订单OrderId或者商品的ProductId等。

消费者顺序消费

@Component
@RocketMQMessageListener(consumerGroup="test-orderrocketmq-group",topic="order-topic",consumeMode=ConsumeMode.ORDERLY)
public class OrderConsumer implements RocketMQListener<Object>
{
    private Logger logger =LoggerFactory.getLogger(getClass());
    @Override
    public void onMessage(Object o)
    {
        String msg=JSON.toJSONString(o);
        logger.info("send msg succss content is:{}", msg);
    }
}

需要注意:RocketMQMessageListener的consumeMode属性默认为ConsumeMode.CONCURRENTLY,实现顺序消息需要将类型需改为ConsumeMode.ORDERLY。

测试示例代码

@RequestMapping("/sendOrderMsg")
     public String sendOrderMsg()
     {
         String orderId="98456231";
         orderProduce.sendOrderMessage(orderId);
         return "success";
     }

测试结果

图片.png

从测试的结果来看,消费者是按照顺序进行消息消费。

总结

本文讲解RocketMQ发送顺序消息需要三个方面共同实现有序才能保证消息的有序性,我们需要结合相应的场景来选择合适的方式实现消息有序性。