前言:
我们发现在一般的消息中间件(MQ)只能保证消息不丢,但是不能保证重复发送等问题。比如在使用Rabbitmq过程中,如何保证消息都能正确的投递被消费,这个是要考虑的问题。 那么可靠性投递所面临的问题有哪些?
- 如果发送的消息重复怎么办。
- 如果消息发送过程中丢了怎么办。
- 如何保证MQ节点成功收到消息。
针对这个问题,我们有以下两个方法,在生产端来实现消息的可靠性投递。
方案一:消息落库,对消息转改进行打标
我们来分析下:
Step1:
Produce所发送的数据是我们的业务数据,比如订单信息。Producer在发送消息之 前给我们的订单信息写入我们的本地数据库(BIZ DB)中,BIZ DB是我们的业务数据库,接 着将要发送到消息存储到消息记录数据库中(MSG DB),这个MSG DB就是存储所有发送给 Rabbitmq的消息记录用的,写入消息记录表中该记录字段status(状态)会有几个值,比如0 表示消息发送中,1表示发送成功,2表示消息发送失败。
Step2:
应用程序要消息发送给Rabbitmq的Broker,这个发送的消息属于confirm模式,发 送后需要Rabbitmq确认。
Step3:
Rabbitmq Broker收到消息后会给Producer一个应答来确认信息,说我已经收到消 息了。
Step4:
紧跟上一步,Producer一直在监听(Listener)Broker所确认的信息,在收到消息 之后,根据收到消息的结果再去更新MSG DB中的消息发送状态,将记录status字段更新为 1。
spring:
application:
name: rabbit-server
# rabbitmq配置
rabbitmq:
# 集群配置
addresses: 192.168.10.138:5672, 192.168.10.138:5673, 192.168.10.138:5674
username: guest
password: guest
virtual-host: /
Step 5:
但是在消息确认这个过程中可能由于网络闪断、MQ Broker端异常等原因导致 回送 消息失败或者异常。这个时候就需要发送方(生产者)对消息进行可靠性投递了,保障消息 不丢失,100%的投递成功!(有一种极限情况是闪断,Broker返回的成功确认消息,但是 生产端由于网络闪断没收到,这个时候重新投递可能会造成消息重复,需要消费端去做幂等 处理)所以我们需要有一个定时任务,(比如每5分钟拉取一下处于中间状态的消息,当然 这个消息可以设置一个超时时间,比如超过1分钟 Status = 0 ,也就说明了1分钟这个时间窗 口内,我们的消息没有被确认,那么会被定时任务拉取出来)。
Step 6:
接下来我们把中间状态的消息进行重新投递 retry send,继续发送消息到MQ ,当 然也可能有多种原因导致发送失败
Step 7:
我们可以采用设置最大努力尝试次数,比如投递了3次,还是失败,那么我们可以 将最终状态设置为Status = 2 ,最后 交由人工解决处理此类问题(或者把消息转储到失败表 中)。
缺点:
消息落库方案虽能提供生产端的可靠性投递,但是,它对数据库进行了两次持久化操作。在实际高并发 业务场景中,每一次持久化操作都需要精心去考量。对于核心链路而言,持久化数据库的时间,能减少 则尽量减少。如果对方在数据库持久化操作时花的时间比你少,那对方系统性能肯定相对而言更好。因 此,如何规避这个问题,尽量减少数据库持久化操作时间,这就是第二种可靠性投递方案,即消息的延 迟投递,做二次确认,回调检查。消息延迟投递方案目的在于减少数据库操作。
方案二:消息延迟投递方案
- Upstream Service表示生产端;
- Downstream Service表示消费端;
- MQ Broker可能是一个MQ集群;
- Callback Service表示回调服务;
第一步: 先将订单业务入库,然后把消息发送到broker端的一个队列1。注意这次,我并不是再把我订单消息又存储到另外一个数据库中,这里只进行一次入库操作。
第二步: 第一步发送消息后,设置一个延迟时间,比如五分钟再次发送该消息,这条消息会发送到broker端的队列2;
第三步: 消费端去监听指定的队列1,对消息进行消费处理;
第四步: 消费端把消息真正处理完之后,还要自己内部再生成一条新的消息叫做send confirm 确认。这条confirm的确认消息也会发送到broker端的一个队列3。
第五步: 回调服务会有一个Confirm Listener去监听这个队列3,如果回调服务收到了来自消费端回送的这条confirm消息,那么,回调服务则认为消费端对数据消费成功,对这条消息做一个持久化的存储,即将消息存入MSG DB。
第六步: 五分钟之后,延迟投递的消息到达broker端指定队列2。回调服务会去监听队列2,如果延迟消息到达队列2,那么回调服务就会去检查MSG DB数据库,查看这条消息是否已经被消费端消费。
注意:
若MSG DB中存在记录,则回调服务什么都不做。若MSG DB中不存在记录,说明消费者一直没有返回 响应数据或者在返回过程中由于网络原因导致返回失败,这时。回调服务会主动发起RPC通信,发送一 套resend的命令(带上消息的id),告知生产者刚才发的这条消息未找到,重新重新发送。然后,生产 端再重复执行第一步。
消息延迟投递优点:
少做一次DB的存储,提高性能; 可能最开始,若执行两次insert持久化操作,可能最多每秒1000单,但如果只持久化一次,可能 就是每秒2000单,这样就相当于节省一台服务器了,而且减少了这种两次持久化数据库操作可能 出现的问题。 异步补偿机制,提供可靠性:两个DB实现解耦。主流核心链路:订单消息入库,生产端发送订单 消息到broker端,消费者负责监听队列进行消费。Callback提供的是一个补偿服务,它不是业务 高峰期的核心链路,而是将它拆出来作为一个单独的服务进行消息的异步补偿。
总结:
消息延迟投递方案最大限度的节省了一次数据信息落库的操作,提高整个高并发性能。同 时,回调服务提供的异步补偿机制,让生产端可靠性投递有了进一步保障。