RocketMQ中的消息种类以及消费模式
前言
在前文中我们已经介绍了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的基本概念
都差不多了,现在就开始动手用起来
吧