前言
上一讲我们讲了MQ实际工作中常见的应用场景,这一节讲一下消息的可靠性,如果对MQ掌握程度比较高的铁子,可以不用看,节省您宝贵的时间。
消息的大致链路
消息从投递到消费需要考虑如下几个问题
- 生产者的消息是否成功投递到消息队列?
- 消息队列的消息会不会丢失?(MQ宕机的情况
- 消费者能否一定能消费到消息?
- 消息异常重试,是否考虑幂等性?
- 消息执行的结果异常,有没有补偿的方案?
以上这几点是使用MQ队列必须考虑的情况,尤其是商业项目,你要做好周密的方案,因为你的消息对于公司来说其实都是数据资产,比如广告行业的pv信息,广告一般计费都是按照pv计价的,消息丢失那就是严重的生产事故。
举例说明
业务场景
电商场景中,用户下单消费成功后,给用户增加对应的奖励积分,因为消费扣款是主业务,送积分其实并不是主流程业务,所以可以通过MQ进行异步处理。下面会介绍4种消息投递的方式,看看这个业务中消息投递的一个过程,我们一起对比一下他们可能存在的问题以及最终比较好的可取的方案。
方案① 在业务事务中投递消息
可能出现异常的情况:
情况(1)
步骤③出现异常,消息投递失败,因为开始了事务,这样会导致订单生成失败,就直接影响到业务主流程;
情况(2)
步骤④发生异常,其他步骤成功:商品下单失败,消息投递成功,给用户增加了积分;因为电商的积分一般是可以兑换东西的,所以间接性地也是造成了损失,虽然不是致命的,还是不可取。
方案② 先进行主业务事务的提交,再进行消息投递
这个顺序的话,如果步骤④发生异常,其他步骤成功:导致商品下单成功,投递消息失败,用户未增加积分,这给用户会造成不好的体验感,这种方式也不可取。
方案③ 事务消息(分两阶段投递)
(1)新建一张消息记录表(order_message_record)
假设这张表有以下几个字段id, order_no,status(默认值是0,表示消息待投递),content
(2) 流程如下
(3)方案说明
这种方式借助了数据库的事务,业务和消息记录作为了一个原子操作,业务成功之后,消息记录必定是存在的。
(4)可能出现的异常情况
若步骤④执行成功,步骤⑤失败了,会导致业务执行成功,而消息投递失败,这样用户购物完,说好的送积分却没送,体验感就不好了,此时我们需要有个job对待发送的消息进行补偿投递。
(5)消息补偿措施
这个job负责从order_message_record表中查询出状态为0记录,重新投递。
对于投递失败的,采用衰减的方式进行重试,比如第1次失败了,则10秒后,继续重试,若还是失败,则再过20秒,再次重试,需要设置一个最大重试次数,最终还是投递失败,则需要告警+人工干预。
这种方案可靠性会相对高一些,成本其实就是消耗一些服务器的资源。还算可以接受。
方案④ 独立拆分消息服务
单独拆分一个消息服务,对于商业项目而言,需要用到MQ的场景一般会比较多。单独新建一个消息服务,负责消息的落库、将消息发送投递到mq,注意这里新增的一个消息服务可以是一个SpringBoot应用。
- (1) 新建一个消息日志表
假设表名叫order_message_log
- (2)新建一个消息表
假设表名叫做order_message_record,有如下几个字段
id:主键,消息id
msg_log_id:业务方order_message_log表的id
body:消息体
msg_log_url:业务方order_message_log记录回查的接口
status:状态,0:待投递,1:投递成功,2:投递失败
fail_msg:投递失败原因
- (3)大致流程
- (4)可能出现的问题
若步骤⑥失败,消息服务order_message_record表中的这条消息,将处于待发送状态,但是业务库订单已经生成了,以及order_message_log表也是有记录的,对于这种情况,消息服务需新增一个job,对于order_message_record表中记录为0的消息,拿到order_message_record表中的msg_log_id去回查msg_log_url这个接口,去查一下业务库中的t_msg_log 表是否有记录,有记录说明业务是执行成功的,此时消息服务补发消息到MQ就可以了;对于回查不到的,有可能业务方本地事务还未提交,不能认定为业务方本地事务执行失败了,建议隔一段时间之后,再清理下这种消息。
如何确保消息到达MQ后,在MQ这边不会丢失?
-
有些MQ为了性能,收到消息后,会将消息放在内存中,并没有立即持久化到磁盘,此时MQ挂了,消息会丢失。
-
若要确保MQ收到消息后,消息不会丢失,则收到投递过来的消息后,立即持久化,这个操作基本上所有的MQ都是支持的,使用的时候配置一下就可以了。
说明: 这个其实是运维干的事情,但是自己搞事情的话,那就得自己干了,其实也是基础知识,一般是安装MQ的时候进行配置一下即可
-
为了防止MQ单节点故障,MQ还需要做主备,这样才可以最大限度的确保消息不会丢失。
消费者如何确保消息一定会被消费?
消费者消费消息,涉及到网络通信,网络通信存在不可靠的因素,可能会失败,导致消息没有被接收或者消费者消费之后没有进行确认,就会出现该消息再次消费的情况,所以需要做幂等处理,这种方式可以确保消息必然会被成功消费一次,并且就算被多次消费,结果也要是一致的。
写在最后
以上就是关于消息可靠性知识点的讲解,面试的时候,其实理解这一些也差不多了,实际业务万变不离其中,也没有说本文就是最优解,只要是能做到消息的可靠性保证就行,当然性能、数据存储这些都是成本的一部分,所以多思考一些情况总是好的,尤其是你自己搞事情的时候,缜密的思维可以让你省不少钱,我们团队的财经号"韭盾",因为每天要处理非常多的数据,保证为我们的用户每天可以收到最新的数据,MQ这一块真的很关键。对钱感兴趣的铁子可以微信搜索“韭盾”公众号,好了,今天的内容就先分享到这里了,咱们下期再见,这个专栏会继续更新。See you later。