【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?

2021年11月23日 阅读数:2
这篇文章主要向大家介绍【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

点击上方“Java精选”,选择“设为星标”java

别问别人为何,多问本身凭什么!面试

下方有惊喜留言必回,有问必答!数据库

天天 08:35 更新文章,天天进步一点点...网络

咱们都知道,消息从生产端到消费端消费要通过3个步骤:异步

  1. 生产端发送消息到RabbitMQ;
  2. RabbitMQ发送消息到消费端;
  3. 消费端消费这条消息;

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_数据库


这3个步骤中的每一步都有可能致使消息丢失,消息丢失不可怕,可怕的是丢失了咱们还不知道,因此要有一些措施来保证系统的可靠性。这里的可靠并非必定就100%不丢失了,磁盘损坏,机房爆炸等等都能致使数据丢失,固然这种都是极小几率发生,能作到99.999999%消息不丢失,就是可靠的了。下面来具体分析一下问题以及解决方案。ide

生产端可靠性投递

生产端可靠性投递,即生产端要确保将消息正确投递到RabbitMQ中。生产端投递的消息丢失的缘由有不少,好比消息在网络传输的过程当中发生网络故障消息丢失,或者消息投递到RabbitMQ时RabbitMQ挂了,那消息也可能丢失,而咱们根本不知道发生了什么。针对以上状况,RabbitMQ自己提供了一些机制。性能

事务消息机制

事务消息机制因为会严重下降性能,因此通常不采用这种方法,我就不介绍了,而采用另外一种轻量级的解决方案——confirm消息确认机制。atom

confirm消息确认机制

什么是confirm消息确认机制?顾名思义,就是生产端投递的消息一旦投递到RabbitMQ后,RabbitMQ就会发送一个确认消息给生产端,让生产端知道我已经收到消息了,不然这条消息就可能已经丢失了,须要生产端从新发送消息了。spa

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_java_02


经过下面这句代码来开启确认模式:3d

channel.confirmSelect();// 开启发送方确认模式

而后异步监听确认和未确认的消息:

channel.addConfirmListener(new ConfirmListener() {
//消息正确到达broker
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("已收到消息");
//作一些其余处理
}

//RabbitMQ由于自身内部错误致使消息丢失,就会发送一条nack消息
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("未确认消息,标识:" + deliveryTag);
//作一些其余处理,好比消息重发等
}
});

这样就可让生产端感知到消息是否投递到RabbitMQ中了,固然这样还不够,稍后我会说一下极端状况。

消息持久化

那消息持久化呢?咱们知道,RabbitMQ收到消息后将这个消息暂时存在了内存中,那这就会有个问题,若是RabbitMQ挂了,那重启后数据就丢失了,因此相关的数据应该持久化到硬盘中,这样就算RabbitMQ重启后也能够到硬盘中取数据恢复。那如何持久化呢?

message消息到达RabbitMQ后先是到exchange交换机中,而后路由给queue队列,最后发送给消费端。

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_队列_03


全部须要给exchange、queue和message都进行持久化:

exchange持久化:

//第三个参数true表示这个exchange持久化
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);

queue持久化:

//第二个参数true表示这个queue持久化
channel.queueDeclare(QUEUE_NAME, true, false, false, null);

message持久化:

//第三个参数MessageProperties.PERSISTENT_TEXT_PLAIN表示这条消息持久化
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));

这样,若是RabbitMQ收到消息后挂了,重启后会自行恢复消息。另外面试题资料公众号Java精选,回复Java面试,获取最新面试资料,支持在线随时随地刷题。

到此,RabbitMQ提供的几种机制都介绍完了,但这样还不足以保证消息可靠性投递RabbitMQ中,上面我也提到了会有极端状况,好比RabbitMQ收到消息还没来得及将消息持久化到硬盘时,RabbitMQ挂了,这样消息仍是丢失了,或者RabbitMQ在发送确认消息给生产端的过程当中,因为网络故障而致使生产端没有收到确认消息,这样生产端就不知道RabbitMQ到底有没有收到消息,就很差作接下来的处理。

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_数据库_04


因此除了RabbitMQ提供的一些机制外,咱们本身也要作一些消息补偿机制,以应对一些极端状况。接下来我就介绍其中的一种解决方案——消息入库。

消息入库

消息入库,顾名思义就是将要发送的消息保存到数据库中。

首先发送消息前先将消息保存到数据库中,有一个状态字段status=0,表示生产端将消息发送给了RabbitMQ但还没收到确认;在生产端收到确认后将status设为1,表示RabbitMQ已收到消息。这里有可能会出现上面说的两种状况,因此生产端这边开一个定时器,定时检索消息表,将status=0而且超过固定时间后(可能消息刚发出去还没来得及确认这边定时器恰好检索到这条status=0的消息,因此给个时间)还没收到确认的消息取出重发(第二种状况下这里会形成消息重复,消费者端要作幂等性),可能重发还会失败,因此能够作一个最大重发次数,超过就作另外的处理。

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_面试_05


这样消息就能够可靠性投递到RabbitMQ中了,而生产端也能够感知到了。

消费端消息不丢失

既然已经可让生产端100%可靠性投递到RabbitMQ了,那接下来就改看看消费端的了,如何让消费端不丢失消息。

默认状况下,如下3种状况会致使消息丢失:

  • 在RabbitMQ将消息发出后,消费端还没接收到消息以前,发生网络故障,消费端与RabbitMQ断开链接,此时消息会丢失;
  • 在RabbitMQ将消息发出后,消费端还没接收到消息以前,消费端挂了,此时消息会丢失;
  • 消费端正确接收到消息,但在处理消息的过程当中发生异常或宕机了,消息也会丢失。

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_rabbitmq_06


其实,上述3中状况致使消息丢失归根结底是由于RabbitMQ的自动ack机制,即默认RabbitMQ在消息发出后就当即将这条消息删除,而无论消费端是否接收到,是否处理完,致使消费端消息丢失时RabbitMQ本身又没有这条消息了。

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_面试_07


因此就须要将自动ack机制改成手动ack机制。

消费端手动确认消息:

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
try {
//接收到消息,作处理
//手动确认
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
} catch (Exception e) {
//出错处理,这里可让消息重回队列从新发送或直接丢弃消息
}
};
//第二个参数autoAck设为false表示关闭自动确认机制,需手动确认
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {});

这样,当autoAck参数置为false,对于RabbitMQ服务端而言,队列中的消息分红了两个部分:一部分是等待投递给消费端的消息;一部分是已经投递给消费端,可是尚未收到消费端确认信号的消息。若是RabbitMQ一直没有收到消费端的确认信号,而且消费此消息的消费端已经断开链接或宕机(RabbitMQ会本身感知到),则RabbitMQ会安排该消息从新进入队列(放在队列头部),等待投递给下一个消费者,固然也有能仍是原来的那个消费端,固然消费端也须要确保幂等性。

好了,到此从生产端到RabbitMQ再到消费端的全链路,就能够保证数据的不丢失。

 

>Java精选面试题<

3000+ 道面试题在线刷,最新、最全 Java 面试题!

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_面试_08

【173期】面试官问:引入 RabbitMQ 后,你如何保证全链路数据 100% 不丢失?_面试_09