以上SEND_OK、COMMIT_MESSAGE、ROLLBACK_MESSAGE 均是client jar提供的状态,在MQ服务器内部是一个数字。
TransactionCheckListener 是在消息的commit或者rollback消息丢失的情况下才会回调(上图中灰色部分)。这种消息丢失只存在于断网或者RocketMQ集群挂了的情况下。当RocketMQ集群挂了,如果采用异步刷盘,存在1s内数据丢失风险,异步刷盘场景下保障事务没有意义。所以如果要核心业务用RocketMQ解决分布式事务问题,建议选择同步刷盘模式。
2. 多系统之间数据一致性(多方事务)
====================
当需要保证多方(超过2方)的分布式一致性,上面的两方事务一致性(通过RocketMQ的事务性消息解决)已经无法支持。这个时候需要引入TCC模式思想(Try-Confirm-Cancel,不清楚的自行百度)。
以上图交易系统为例:
1)交易系统创建订单(往DB插入一条记录),同时发送订单创建消息。通过RocketMQ事务性消息保证一致性
2)接着执行完成订单所需的同步核心RPC服务(非核心的系统通过监听MQ消息自行处理,处理结果不会影响交易状态)。执行成功更改订单状态,同时发送MQ消息。
3)交易系统接受自己发送的订单创建消息,通过定时调度系统创建延时回滚任务(或者使用RocketMQ的重试功能,设置第二次发送时间为定时任务的延迟创建时间。在非消息堵塞的情况下,消息第一次到达延迟为1ms左右,这时可能RPC还未执行完,订单状态还未设置为完成,第二次消费时间可以指定)。延迟任务先通过查询订单状态判断订单是否完成,完成则不创建回滚任务,否则创建。 PS:多个RPC可以创建一个回滚任务,通过一个消费组接受一次消息就可以;也可以通过创建多个消费组,一个消息消费多次,每次消费创建一个RPC的回滚任务。 回滚任务失败,通过MQ的重发来重试。
以上是交易系统和其他系统之间保持最终一致性的解决方案。
3. 案例分析
========
- 单机环境下的事务示意图
==============
如下为A给B转账的例子。
1锁定A的账户
2锁定B的账户
3检查A账户是否有1元
4A的账户扣减1元
5给B的账户加1元
6解锁B的账户
7解锁A的账户
步骤动作
以上过程在代码层面,甚至可以简化到在一个事物中,执行两条sql语句。
2) 分布式环境下事务
和单机事务不同,A、B账户可能不在同一个DB中,此时无法像在单机情况下使用事务来实现。
此时可以通过一下方式实现,将转账操作分成两个操作。
a) A账户
1锁定A的账户
2检查A账户是否有1元
3A的账户扣减1元
4解锁A的账户
步骤动作
b) MQ消息
A账户数据发生变化时,发送MQ消息,MQ服务器将消息推送给转账系统,转账系统来给B账号加钱。
c) B账户
1锁定B的账户
2给B的账户加1元
3解锁B的账户
步骤动作
四、 顺序消息
RocketMq有3中消息类型
===============
1. 普通消费
2. 顺序消费
3. 事务消费
顺序消费场景:在网购的时候,我们需要下单,那么下单需要假如有三个顺序:
第一、创建订单
第二:订单付款
第三:订单完成
也就是这三个环节要有顺序,这个订单才有意义,RocketMQ可以保证顺序消费。
RocketMQ 实现顺序消费的原理:produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一个线程去消费消息
注意:是把把消息发到同一个队列(queue),不是同一个topic,默认情况下一个topic包括4个queue
1. 顺序消息缺陷
发送顺序消息,无法利用集群Fail Over特性,消费顺序消息的并行度依赖于队列数量队列热点问题,个别队列由于哈希不均导致消息过多,消费速度跟不上,产生消息堆积问题遇到消息失败的消息,无法跳过,当前队列消费暂停。
2. 原理
produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一个线程去消费消息。
注意:把消息发到同一个队列(queue),不是同一个topic,默认情况下一个topic包括4个queue
3. 扩展
可以通过实现发送消息的队列选择器方法,实现部分顺序消息。
举例:比如一个数据库通过MQ来同步,只需要保证每个表的数据是同步的就可以。解析binlog,将表名作为队列选择器的参数,这样就可以保证每个表的数据到同一个对列里面,从而保证表数据的顺序消费
五、 最佳实践