2023-08-01
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/126

我在《分布式框架之高性能:消息有序性》中,曾经介绍过RabbitMQ和Kafka实现消息有序性的基本思路。在RocketMQ中,提供了一种顺序消息类型。所谓顺序消息(FIFO 消息),是一种严格按照顺序来发布和消费的消息。顺序发布和顺序消费是指对于指定的一个Topic: 生产者按照一定的先后顺序发布消息;消费者按照既定的先后顺序订阅消息,即先发布的消息一定会先被客户端接收到

一、大数据传输问题

还记得第一章系统背景中的“大数据传输问题”吗?我们今天就以这个问题为引子,来讲讲MQ中的消息有序性。先来回顾下系统存在的问题:

202308012101444041.png

“大数据系统”需要从订单数据库里查询订单数据,由于数据库中每日的订单数据通常是比较多的,频繁让外部系统直接通过数据库查数会影响订单系统本身的性能。试想以下,在高峰场景下,订单数据库本身去支撑正常业务流量就很吃力了,此时CPU和内存都比较吃紧,这时再来个“大数据系统”从我这里大量查数,简直是添乱。

所以,我们肯定不能让外部系统直接来访问订单系统的数据库。通常来说,对于这种数据采集操作,我们一般会把整个库的数据都直接copy一份给外部系统。

1.1 解决方案

怎么拷贝?对于MySQL,业界目前最常用的做法是采用开源数据库中间件 Canal ,让Canal去监听订单数据库的binlog,然后把这些binglog发送到MQ中去,接着大数据系统自己从MQ里获取binlog,落地到自己的大数据存储中去,然后对自己的存储中的数据进行计算得到数据报表即可:

202308012101454952.png

那么问题来了,binlog记录的是数据库的操作,比如rinset、update、delete,假设订单系统针对某笔订单的数据操作是下面这样的:

  1. 先插入一条订单;
  2. 再更新这条订单记录的状态。

那么binlog也会按照上述顺序记录日志,但是大数据系统订阅binlog记录时,可能先获取到更新操作记录,再获取到插入操作,那么本地还原时就会出现问题。如下图:

202308012101461033.png

出现这种问题的根本原因就是发布/订阅消息时的顺序不一致造成的。所以,要解决这个问题,就必须做到以下两点:

  1. 针对同一个订单的所有SQL操作进入同一个ConsumeQueue,这样首先可以保证只会有一个Consumer实例去顺序消费这个ConsumeQueue中的数据;
  2. Canal将消息发送到RokectMQ时,也必须有序,也就是先发送insert、后发送update,RockectMQ要保证先发送的消息先入队ConsumeQueue。

1.2 失败重试问题

做到以上两点是否就一定能保证消息有序呢?不一定,如何Consumer实例中途消费失败导致消息重发,仍然可能出现消息乱序问题。

我们之前在《RocketMQ重复消费》一章讲过,在Consumer实例处理消息时,可能会因为自身处理逻辑执行失败,返回RECONSUME_LATER状态,这种情况下,Broker过一会儿会自动给消费者重新发送这个消息。但是此时消费者实例会立即消费下一条消息,也就是这个订单的update binlog操作,此时万一执行成功了,等后面那条insert消息到达执行时,又会出现消息乱序的问题。

所以对于有序消息方案,当Consumer实例遇到消息处理失败的场景,就必须返回SUSPEND_CURRENT_QUEUE_A_MOMENT这个状态,意思是“先不要把其它消息发给我,要等待重试“。

二、RocketMQ中的顺序消息

在RocketMQ中,有两种类型的顺序消息: 全局顺序消息分区顺序消息

2.1 全局顺序消息

对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序来发布和消费。

适用场景
适用于性能要求不高,所有的消息严格按照 FIFO 原则来发布和消费的场景。

示例
在证券处理中,以人民币兑换美元为 Topic,在价格相同的情况下,先出价者优先处理,则可以按照 FIFO 的方式发布和消费全局顺序消息。

2.2 分区顺序消息

对于指定的一个 Topic,所有消息根据Sharding Key进行区块分区。同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。Sharding Key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。

适用场景
适用于性能要求高,以 Sharding Key 作为分区字段,在同一个区块中严格地按照 FIFO 原则进行消息发布和消费的场景。

示例

  • 用户注册需要发送发验证码,以用户 ID 作为 Sharding Key,那么同一个用户发送的消息都会按照发布的先后顺序来消费。
  • 电商的订单创建,以订单 ID 作为 Sharding Key,那么同一个订单相关的创建订单消息、订单支付消息、订单退款消息、订单物流消息都会按照发布的先后顺序来消费。

三、总结

本章,我们介绍了RockectMQ中的顺序消息,并以此解决了我们的背景案例中的一个遗留问题——大数据传输问题。在RocketMQ中,提供了两种类型的顺序消息:全局顺序消息、分区顺序消息。读者可以自行参考RocketMQ的官方文档进行学习。

阅读全文