之前提到发送消息的时候,可能存在消息的丢失,也就是说可能消息根本就没有进入到MQ就丢了,然后没有解释过多的东西就直接切入了RocketMQ事务消息的方案,其实通过RocketMQ事务消息机制的研究,现在可以确信一点,如果使用事务消息机制去发送消息到MQ,一定是可以保证消息必然发送到MQ的,不会丢,我来为大家科普一下关于rocketmq能解决分布式事务吗?下面希望有你要的答案,我们一起来看看吧!

rocketmq能解决分布式事务吗(RocketMQ-解决发送消息零丢失一定要使用事务消息方案吗)

rocketmq能解决分布式事务吗

之前提到发送消息的时候,可能存在消息的丢失,也就是说可能消息根本就没有进入到MQ就丢了,然后没有解释过多的东西就直接切入了RocketMQ事务消息的方案,其实通过RocketMQ事务消息机制的研究,现在可以确信一点,如果使用事务消息机制去发送消息到MQ,一定是可以保证消息必然发送到MQ的,不会丢。

但是这个事务消息机制其实挺复杂的,先得发送half消息,然后还得发送rollback/commit的请求,要是中间有点什么问题,MQ还得回调你的接口。

我们真的有必要使用这么复杂的机制去确保消息到达MQ,而且绝对不会丢吗?毕竟这么复杂的机制完全有可能导致整体性能比较差,而且吞吐量比较低,是否有更加简单的方法来确保消息一定可以到达MQ呢?

能不能基于重试机制来确保消息到达MQ?

想到这里,可能内心已经有一个想法了,就是我们之前觉得发消息到MQ,无非就是觉得可能半路上消息给丢失了,然后消息根本没有进入到MQ中,我们也没做什么额外的措施,就导致消息找不回来了。

那么我们先搞清楚一个问题,我们发送消息到MQ,然后我们可以等待MQ返回响应给我们,在什么样的情况下,MQ会返回响应给我们呢?

答案是显而易见的,就是MQ收到消息之后写入本地磁盘文件了,当然这个时候可能仅仅是写入os cache中,但是只要他写入自己本地存储了,就会返回响应给我们。

那么只要我们在代码中发送消息到MQ之后,同步等待MQ返回响应给我们,一直等待,如果半路有网络异常或者MQ内部异常,我们肯定会收到一个异常,比如网络错误,或者请求超时之类的。

如果我们再收到异常之后,就认为消息发送MQ失败了,然后再次重试尝试发送消息到MQ,接着再次同步等待MQ返回响应给我们,这样反复重试,是否可以确保消息一定会到达MQ?

理论上似乎存在一些短暂网络异常的场景下,我们是可以通过不停的重试去保证消息到达MQ的,因为如果短时间网络异常了,消息一直没法发送,我们只要不停的重试,网络一旦恢复了,消息就可以发送到MQ了。

如果要是反复重试多次发现一直没有办法把消息投递到MQ,此时我们就可以直接让订单系统回滚之前的流程,判定本次订单支付交易失败了。

看起来这个简单的同步发送消息 反复重试的方案,也可以做到保证消息一定可以投递到MQ中。

但是如果是在比较复杂的订单业务场景中,仅仅采用同步发消息 反复重试多次的方案去确保消息绝对投递到MQ中,似乎还是不够的。

先执行订单本地事务,还是先发消息到MQ?

如果我们先执行本地事务,接着再发送消息到MQ,看起来伪代码可能是这样的:

try { // 执行订单本地事务 orderService.finishOrderPay(); // 发送消息到MQ去 producer.sendMessage(); } catch (Exception e) { // 如果发送消息失败了,进行重试 for(int i=0;i < 3; i ) { // 重试发送消息 } // 如果多次重试发送消息之后,还是不行 // 回滚本地订单事务 orderService.rollbackOrderPay(); }

上面这段代码看起来似乎天衣无缝,先执行订单本地事务,接着发送消息到MQ,如果本地事务执行失败了,则不会继续发送消息到MQ了。

如果订单事务执行成功了,发送MQ失败了,自动进行几次重试,重试如果一直失败,就回滚订单。

但是这里有一个问题,假设你刚执行完订单本地事务,结果还没等到你发送消息到MQ,结果你的订单系统突然崩溃了,这就导致你的订单状态可能已经修改为了"已完成",但是消息却没有发送到MQ中去,这就是这个方案最大的隐患。

如果出现这种场景,那你的多次重试发送MQ之类的代码根本没有机会执行,而且订单本地事务还已经执行成功了,你的消息还没发送出去,消费者肯定没有办法消费。

把订单本地事务和重试发送MQ消息放到一个事务代码中

接着考虑下一个问题,这时候可能有一个新的想法,如果把订单本地事务代码和发送MQ消息的代码放到一个事务代码中呢?

@Transactional public void payOrderSuccess() { try { // 执行订单本地事务 orderService.finishOrderPay(); // 发送消息到MQ去 producer.sendMessage(); } catch (Exception e) { // 如果发送消息失败了,进行重试 for(int i=0;i < 3; i ) { // 重试发送消息 } // 如果多次重试发送消息之后,还是不行 // 抛出异常,回滚本地事务 throw new XXXException(); } }

上面这个代码看起来似乎解决了我们的问题,就是在这个方法上加入事务,在这个事务方法中,我们哪怕执行了orderService.finishOrderPay(),但是其实也仅仅执行了一些增删改查的SQL语句,还没提交订单本地事务。

如果发送消息失败了,而且多次重试还不奏效,则抛出异常会自动回滚本地事务。

如果你刚执行了 orderServicec.finishOrderPay(),结果订单系统直接崩溃了,此时订单本地事务会回滚,因为根本没提交过。

但是对于这个方案,还是非常的不理想,原因就出在那个MQ多次重试的地方,假设用户支付成功了,然后支付系统回调通知你的订单系统说有一笔订单支付成功了,这个时候你的订单系统卡在多次重试MQ的代码那里,可能耗时了好几秒中,此时回调通知及的系统早就等不及可能都超时异常了。

而且把重试MQ的代码放在这个逻辑里,可能导致订单系统的这个接口性能很差。

保证业务系统一致性的最佳方案:基于RocketMQ的事务消息机制

综合来看,真正要保证消息一定投递到MQ,同时保证业务系统之间数据完全一致,业内最佳的方案还是基于RocketMQ的事务消息机制。

,