169. 如何保证MQ消息的可靠性(不丢失)


之前我们有一篇文章介绍过MQ的主要使用场景以及常见的问题和解决办法:
141. MQ的7种常用场景
111. 使用MQ可能存在的问题以及解决办法

思维导图如下:
在这里插入图片描述

本文主要介绍一下如何保证消息的可靠性,也就是消息不丢失

一、消息的使用过程

在这里插入图片描述
从上图不难看出,需要从三个方向考虑消息是否可靠投递并被消费了

  1. 如何确保生产者这边业务执行成功,消息一定会投递到MQ?
  2. 如何确保消息到达MQ后,MQ这边不会丢失?
  3. 如何确保消费者一定能消费到这条消息?

二、如何确保生产者这边业务执行成功,消息一定投递成功?

这块涉及到消息投递的整个过程,下面咱们来通过一个案例来了解消息投递的整个过程,以及在这个过程中,如何确保业务执行成功,消息一定会投递成功。

电商中有这样的一个场景

下单成功之后送积分的操作,我们使用mq来实现

下单成功之后,投递一条消息到mq,积分系统消费消息,给用户增加积分

下面会介绍4种方式,来看下这个业务中消息投递的一个过程。

消息投递方式一:业务事务中投递消息

正常流程

  • step1:开启本地事务
  • step2:生成购物订单
  • step3:投递消息到mq
  • step4:提交本地事务(这里仅控制DB语句是否回滚,只是是否回滚由step3是否成功决定)

这种方式是将发送消息放在了事务提交之前。

异常情况

  • step3发生异常:导致step4失败,商品下单失败,直接影响到商品下单业务
  • step4发生异常,其他step成功:商品下单失败,消息投递成功,给用户增加了积分

消息投递方式二:业务事务提交后、后投递消息

下面我们换种方式,我们将发送消息放到事务之后进行。

正常流程

  • step1:开启本地事务
  • step2:生成购物订单
  • step3:提交本地事务
  • step4:投递消息到mq

异常情况

  • step4发生异常,其他step成功:导致商品下单成功,投递消息失败,用户未增加积分

上面两种是比较常见的做法,也是最容易出错的。

消息投递方式三:事务消息(二阶段投递)

需要再本地业务库添加一张本地消息表(t_msg_record)

  • id
  • body:消息体
  • status:消息状态:0:待投递,1:投递成功,2:投递失败
  • 根据具体业务,可能还需要其他必要字段,用于消息重试发送

正常流程

  • step1:开启本地事务
  • step2:生成购物订单
  • step3:本地库中插入一条需要发送消息的记录t_msg_record,status为0(待投递)
  • step4:提交本地事务
  • step5:若事务提交成功,则投递消息到MQ,然后将t_msg_record中的status置为1(投递成功);若本地事务提交失败,则将t_msg_record表中的消息记录删掉

注:这里step2和step3是在同一个数据库中的不同表,可以直接用DB框架保证事务性

说明

这种方式借助了数据库的事务,业务和消息记录作为了一个原子操作,业务成功之后,消息记录必定是存在的。

异常情况

若step4成功,step5失败了,会导致下单业务执行成功,而消息投递失败,此时我们需要有个定时旁路job对待发送的消息进行补偿投递。

消息投递补偿job

这个job负责从本地t_msg_record表中查询出状态为0记录,重新投递。

对于投递失败的,采用衰减的方式进行重试,比如第1次失败了,则10秒后,继续重试,若还是失败,则再过20秒,再次重试,需要设置一个最大重试次数,最终还是投递失败,则需要告警+人工干预。

消息投递方式四:独立出来一个消息服务

增加一个消息服务消息库,负责消息的落库、将消息发送投递到mq,注意这里新增的一个消息服务可以是一个微服务(MQ消息中台)。

业务库需要添加一张消息日志表(t_msg_log)

  • id
  • bus_id:业务id
  • bus_type:业务类型

消息服务需要一张消息表(t_msg)

  • id:主键,消息id
  • msg_log_id:业务方t_msg_log表的id
  • body:消息体
  • msg_log_url:业务方t_msg_log记录回查的接口
  • status:状态,0:待投递,1:投递成功,2:投递失败
  • fail_msg:投递失败原因

消息投递的过程

  • step1:开启本地事务
  • step2:生成购物订单
  • step3:本地库t_msg_log表写入一条记录:insert into t_msg_log (bus_id,bus_type) values (‘订单id’,‘CREATE_ORDER’)
  • step4:调用消息服务,需携带(t_msg_log.id,消息体,消息日志回查的url),消息服务接收到请求后,向t_msg表插入记录(status=0,待发送),并返回消息id:msg_id
  • step5:提交本地事务
  • step6:如果上面都成功,使用step4中的msg_id调用消息服务,消息服务则将消息投递到mq中,修改消息记录状态为投递成功(t_msg.status=1);如果上面有失败的情况,则消息服务将消息删掉

可能存在的问题

若step6失败,消息服务t_msg表中的这条消息,将处于待发送状态,但是业务库订单已经生成了,以及t_msg_log表也是有记录的,对于这种情况,消息服务需新增一个job,对于t_msg表中记录为0的消息,拿到t_msg表中的msg_log_id去回查msg_log_url这个接口,去查一下业务库中的t_msg_log 表是否有记录,有记录说明业务是执行成功的,此时消息服务补发消息到MQ就可以了;对于回查不到的,有可能业务方本地事务还未提交,不能认定为业务方本地事务执行失败了,建议等到1天之后,再清理下这种消息。

三、如何确保消息到达MQ后,在MQ这边不会丢失?

有些MQ为了性能,收到消息后,会将消息放在内存中,并没有立即持久化到磁盘,此时MQ挂了,消息会丢失。

若要确保MQ收到消息后,消息不会丢失,则收到投递过来的消息后,立即持久化,这个操作基本上所有的MQ都是支持的,使用的时候配置一下就可以了。

为了防止MQ单节点故障,MQ还需要做主备,这样才可以最大限度的确保消息不会丢失。

四、消费者如何确保消息一定会被消费?

消费者这边可以采用下面的过程,可以确保消息一定会被消费。

  • step1:从MQ中拉取消息

  • step2:执行业务逻辑(需要做幂等)

  • step3:通知MQ删除这条消息

由于step3这个步骤涉及到网络通信,网络通信存在不可靠的因素,可能会失败,导致消息没有被删掉,就会出现该消息再次消费的情况,所以step2需要做幂等处理,这种方式可以确保消息必然会被成功消费一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值