RocketMQ中的消息种类以及消费模式
- 前言
- 消息的种类
- 按消息的发送方式
- 同步消息
- 异步消息
- 单向消息
- 按消息的种类
- 普通消息(Normal Message)
- 顺序消息(Orderly Message)
- 延时消息(Delay Message)
- 事务消息(Transaction Message)
- 事务消息的执行过程
- 半事务消息(Half-Message)
- 为什么要先发再执行
- 事务消息的限制
- 消息的消费模式
- 集群消费模式
- 广播消费模式
- 两种消费模式简单对比
前言
在前文中我们已经介绍了RocketMQ的基本概念,本文来介绍消息的种类以及消息常用的消费模式
当然大家也可以查看RocketMQ的官网的官方文档
消息的种类
我们知道不同业务可能要用到不同消息,那么RocketMQ中的消息种类有哪些呢?
按消息的发送方式
同步消息
发出消息后,需要等待收到服务端的接收确认信息之后再发送下一个消息。
假如下面的生产者要发送两个消息:消息1和消息2,生产者先发送消息1,在发送完消息1之后并不会继续发送消息2,而是会等待RocketMQ接收后的响应。

然后RocketMQ会发送一个接收成功的消息给生产者

然后再发送第二个消息,再等待接收成功的响应,以此往复。。。。

也就是每次生产者发送完消息,都要等待RocketMQ发给自己接收成功的消息以后才会继续发送消息。
这种一般用于比较重要的消息,比如发送短信。
异步消息
与上述的同步消息略有出入,这种消息不需要等待对方的接收响应就可以继续发送下一个消息。

可能在发送完消息2之后才会收到响应

单向消息
这个不像上面的两个 这种消息不需要RocketMQ的响应

这种一般用于日志性质的消息。
按消息的种类
普通消息(Normal Message)
最常用的消息,没有什特别的要求,只要能正常生产消费即可。
比如一些营销短信和通知短信。
顺序消息(Orderly Message)
对一系列相关消息的消费顺序有要求,必须有先后的消费顺序。
比如系统中的多笔款项转到同一张银行卡,必须保证转账消息的先后顺序,才能确保用户收到款项的正确顺序。
一般是通过设置队列选择器和队列数量将一系列的消息放到同一的队列中实现。
延时消息(Delay Message)
这类比较简单,就是一种在一定时间后才会被消费的消息。
比如,我们的定时生日邮件。
事务消息(Transaction Message)
事务消息是一种特殊的消息类型,它可以保证消息的发送和本地事务的执行结果一致。
看起来不太好理解,我们一个一个看:
消息的发送:就是生产者将消息发送到RocketMQ上的过程
本地事务: 这个更好理解,就是你本地的一个操作集合,这些操作要么一起成功要么一起失败
执行结果的一致: 也就是本地事务成功 我这条消息能保证发送到RocketMQ上被消费 ,本地事务执行失败,该消息就无法被消费
这么一分析是不是有点看得懂了?下面们以一个简单的转账示例来更加形象的理解事务消息:
假如你现在带着你的银行卡去银行的ATM机上转账1000到另外卡上,并且该ATM机的转账业务是以消息的形式交给其他的银行的,那么这样的话我们可以画出这样一个图,来模拟转账的流程:

- 将我要
转账1000的消息发送给RocketMQ上面 RocketMQ收到消息,给ATM机响应:我收到了,你本地扣款1000吧- ATM执行
本地扣款,从你的银行卡上扣除1000余额 RocketMQ将该转账消息交给对应的银行消费者消费,增加收款方余额1000
前提:只要消息放到RocketMQ上,就可以默认消息不会丢失,而是会被顺利执行。
所以我们这里只考虑前三步即可,看出来上述前三步的问题了吗?
本地的扣款是一个本地事务,如果因为某些原因比如突然断电,短路,导致事务没有执行成功,但是消息已经发出去了,会造成什么后果?
没错,会导致你的卡上没有少钱,而转账的卡上多了1000。
银行:

所以为了避免上述情况的发生,我们应该让该消息与本地事务紧密绑定在一起,本地事务成功,该消息能被消费,本地事务失败,就不能被消费。
为了保证消息与本地事务的一致性,我们看看事务消息是怎么做的。
事务消息的执行过程
以上面的业务演示

半事务消息(Half-Message)
RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递"(不会让消费者消费),这种状态下的消息即为半事务消息。
了解了半事务消息,我们来分析上图的流程:
ATM机发送半事务消息给RocketMQ: 我要转账了RocketMQ将该消息存好以后,告诉ATM机:我存好了,你开始本地事务吧,执行完告诉我。- ATM机执行
本地的扣款事务 - ATM机
本地事务执行完成,告诉RocketMQ,这里的本地事务有两种结果:成功-RocketMQ将半事务状态标记为可投递,该消息可以被消费;失败-将回滚事务,不会将半事务消息投递给消费者。也有可能没有给RocketMQ响应,比如直接宕机了,这样的话RocketMQ就会走第五步。 - 当然也有意外比如:ATM
事务执行到一半,碰上歹徒把电断了,就没法告诉RocketMQ本地事务的执行结果了。此时RocketMQ会有一个回查操作(有次数限制,不会无限制回查,超过次数无响应视为失败),即主动询问ATM机那个事务执行完成没有。 - 如果ATM机有幸还有一点电,那么它会
检查一下该事务的状态。 - 然后再走
第四步的操作,告诉RocketMQ:成功-RocketMQ将半事务状态标记为可投递,该消息可以被消费;失败-将回滚事务,不会将半事务消息投递给消费者。没有回应的话,默认为失败。
为什么要先发再执行
在上述的过程中,可以看出是一个先发再执行:先发半事务消息再去执行本地事务。
我们这里可以打一个问号:为什么?既然这么麻烦,为什么我们不本地执行完本地事务再去提交消息?这样本地事务如果执行失败,都不用再去发消息了。
为什么不行?很简单,如果你本地事务执行成功以后再去发送消息,此时如果你的消息发不出去呢?
也就是你本地扣了款,但是消息发不出去呢?比如遇到网络拥堵或者异常,又或者是RocketMQ端宕机,你怎么办?
将相关版本回滚到你提交事务之前的版本吗?要是在你的事务提交之前有很多其他事务已经提交了呢?也就是你的回滚会导致其他的事务的操作丢失。所以这种方式,一旦你本地提交完了事务,而消息却无法发送出去,所要付出的代价太大了。
但使用半事务的机制就可以避免这个问题:如果半事务消息都发不出去,根本不会执行本地的事务。
如果半事务消息发送出去,但是本地事务没有执行或者执行失败,这个半事务消息最终不会被消费,因为收到(或者回查到)的是事务执行失败的状态,会进行回滚。该消息会随你本地的事务的失败而不会被回滚,也就不会被后续的消费者消费。
其实说穿了事务消息就是给消息加了类似事务的“提交“与“回滚”的操作,以此来达到与本地事务的最终结果保持一致性。
事务消息的限制
当然事务消息也有一些限制:仅仅能保证最终一致性,无法保证实时的一致性。
很好理解,也就是我们的本地事务执行完成,半事务消息也发上去也被标记为可投递状态,但是该消息还没被消费者消费。
简单的比方就是:你发起转账了,你余额也扣除了,但是你的另一张卡没有实时收到钱,这时就会导致一个问题: 我本地操作显示成功了,但是我另一张卡没收到钱?
因为此时你的转账消息还没被消费者消费,就导致了这种情况。当然了,最终肯定会到账的,这就是最终一致性。
其他的限制,请参考RocketMQ的官网的官方文档。
消息的消费模式
我们知道有的消息是只能被消费一次的,比如支付消息只能给支付模块消费一次。
当然也有的消息需要被消费多次,比如日志消息要给订阅该主题的各个模块各自消费一次。
集群消费模式
消息只被消费者组内的消费者消费一次。如以下这样:消费者组订阅了支付主题,每个消费者获得的消息队列(queue各不相同),每条消息只被消费一次。

广播消费模式
消息被消费者组内的消费者各消费一次。如以下这样:其中的业务日志模块、业务模块、普通日志模块都订阅了日志主题,那么每条日志消息会给每个消费者各消费一次。

两种消费模式简单对比
| 消费模式 | 优点 | 缺点 |
|---|---|---|
| 集群消费 | 可以实现消息的负载均衡(消息被平分给每个消费者),提高消费的效率和性能 | 如果消费者实例数量大于主题的队列数量,会导致部分消费者无法分配到队列,从而无法消费消息 |
| 广播消费 | 可以实现消息的多播,满足多个消费者同时处理同一条消息的需求 | 会增加消息的网络传输和存储开销,以及消费者的处理压力 |
到这里RocketMQ的基本概念都差不多了,现在就开始动手用起来吧



















