Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
MQ专题
-> MQ专题第2篇:事务消息如何实现?
1、MQ专题第1篇:消息队列使用场景
2、MQ专题第2篇:事务消息如何实现?
3、MQ专题第3篇:消息消费的2种方式:pull方式、push方式,如何选择?
4、MQ专题第4篇:如何确保消息消息至少被成功消费一次?
5、MQ专题第5篇:如何确保消息消费的幂等性?
6、MQ专题第6篇:顺序消息如何实现?
上一篇:MQ专题第1篇:消息队列使用场景
下一篇:MQ专题第3篇:消息消费的2种方式:pull方式、push方式,如何选择?
<div style="display:none"></div> ## 目录 [TOC] ## 电商中有这样的一个场景 1. 下单成功之后送积分的操作,我们使用mq来实现 2. 下单成功之后,投递一条消息到mq,积分系统消费消息,给用户增加积分 我们主要讨论一下,下单及投递消息到mq的操作,如何实现?每种方式优缺点? ## 消息投递方式一:业务事务中投递消息 **过程** - **step1**:开启本地事务 - **step2**:生成购物订单 - **step3**:投递消息到mq - **step4**:提交本地事务 这种方式是将发送消息放在了事务提交之前。 **可能存在的问题** - step3发生异常:导致step4失败,商品下单失败,直接影响到商品下单业务 - step4发生异常,其他step成功:商品下单失败,消息投递成功,给用户增加了积分 ## 消息投递方式二:先业务事务、后投递消息 下面我们换种方式,我们将发送消息放到事务之后进行。 **过程** - **step1**:开启本地事务 - **step2**:生成购物订单 - **step3**:提交本地事务 - **step4**:投递消息到mq **可能会出现的问题** step4发生异常,其他step成功:导致商品下单成功,投递消息失败,用户未增加积分 上面两种是比较常见的做法,也是最容易出错的。 ## 消息投递方式三:通过事务消息记录实现 - **step1**:开启本地事务 - **step2**:生成购物订单 - **step3**:本地库中插入一条需要发送消息的记录t_msg_record - **step3**:提交本地事务 - **step5**:新增一个定时器,轮询t_msg_record,将待发送的记录投递到mq中 这种方式借助了数据库的事务,业务和消息记录作为了一个原子操作,业务成功之后,消息日志必定是存在的。解决了前两种方式遇到的问题。如果我们的业务系统比较单一,可以采用这种方式。 对于微服务化的情况,上面这种方式不是太好,每个服务都需要上面的操作;也不利于扩展。 ## 消息投递方式四:2阶段投递消息 增加一个**消息服务**及**消息库**,负责消息的落库、将消息发送投递到mq。 - **step1**:开启本地事务 - **step2**:生成购物订单 - **step3**:当前事务库插入一条日志:生成一个唯一的业务id(bus_id),将bus_id和订单关联起来保存到当前事务所在的库中 - **step4**:调用消息服务:携带bus_id,将消息先落地入库,此时消息的状态为待发送状态,返回消息id(msg_id) - **step5**:提交本地事务 - **step6**:如果上面都成功,调用消息服务,将消息投递到mq中;如果上面有失败的情况,则调用消息服务取消消息的发送 能想到上面这种方式,已经算是有很大进步了,我们继续分析一下可能存在的问题: 1. 系统中增加了一个消息服务,商品下单操作依赖于该服务,业务对该服务依赖性比较高,当消息服务不可用时,整个业务将不可用。 2. 若step6失败,消息将处于待发送状态,此时业务方需要提供一个回查接口(通过bus_id查询),验证业务是否执行成功;消息服务需新增一个定时任务,对于状态为待发送状态的消息做补偿处理,检查一下业务是否处理成功;从而确定消息是投递还是取消发送 3. step4依赖于消息服务,如果消息服务性能不佳,会导致当前业务的事务提交时间延长,**容易产生死锁,并导致并发性能降低**。我们通常是比较忌讳在事务中做远程调用处理的,远程调用的性能和时间往往不可控,会导致当前事务变为一个大事务,从而引发其他故障。 ## 消息投递方式五:2阶段投递消息优化 在以上方式中,我们继续改进,进而出现了更好的一种方式: - **step1**:生成一个全局唯一业务消息id(bus_msg_id),调用消息服务,携带bus_msg_id,将消息先落地入库,此时消息的状态为待发送状态,返回消息id(msg_id) - **step2**:开启本地事务 - **step3**:生成购物订单 - **step4**:当前事务库插入一条日志(将step3中的业务和bus_msg_id关联起来) - **step5**:提交本地事务 - **step6**:分2种情况:如果上面都成功,调用消息服务,将消息投递到mq中;如果上面有失败的情况,则调用消息服务取消消息的发送 若step6失败,消息将处于待发送状态,此时业务方需要提供一个回查接口(通过bus_msg_id查询),验证业务是否执行成功; 消息服务需新增一个定时任务,对于状态为待发送状态的消息做补偿处理,检查一下业务是否处理成功;从而确定消息是投递还是取消发送。 方式五和方式四对比,比较好的一个地方:将调用消息服务,消息落地操作,放在了事务之外进行,这点小的改进其实算是一个非常好的优化,减少了本地事务的执行时间,从而可以提升并发量,阿里有个消息中间件**RocketMQ**就支持方式5这种,大家可以去用用。 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/189/841a9ed8-1a18-4f8c-9306-8df770768cdc.png) ## 投递消息到底选择哪种方式呢? 1. 若我们的系统系统比较小比较单一简单,建议采用方式三 2. 若我们的系统采用微服务的方式,建议使用方式五 <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container