书接上文。这次继续聊一聊MQ,往期文章参考:
对线面试官-为什么要使用MQ
对线面试官 - MQ经典面试题之高可用性及幂等性
面试官:继上次你对MQ的回答,我还有一些疑问想要针对MQ。我们继续聊一聊?
派大星:好的,当然可以。
面试官:OK,那我们继续上次的话题,就是MQ如何保证消息的可靠性,或者说如何保证消息不丢失呢?
派大星:这种情况需要就不同情况进行分析。主要是有三张场景会导致消息丢失的问题。
- 生产者丢失了消息
- MQ丢失了消息
- 消费的时候丢失了消息
面试官:嗯,不错,那你能就每种情况简单聊一聊吗?
派大星:可以,首先我先简单说一下RabbitMQ丢失消息如何解决。每种消息丢失的情况的解决方案大致如下图所示:
- 首先来说一说生产者丢失了消息:
主要场景是:写消息等过程中消息还没有到达MQ的时候,在网络传输的过程中就将消息丢失了;或者消息到了RabbitMQ但是MQ内部错乱没有存储消息导致消息丢失。
解决方案1:可以使用RabbitMQ事务机制
,具体配置如下:
channel.txSelect();
try{
// 发送消息
}catch{
channel.txRollback();
}
channel.txCommit();
但是该种方案也有弊端:因为是事务机制,所以是同步阻塞的,这样就会导致生产者发送消息的吞吐量大大下降
解决方案2:把channel设置成confirm
模式,发送一个消息就不用管了,RabbitMQ如果接收到了这个消息就会回调生产者本地的一个接口,通知你说这条消息已经发送成功并且接收成功,反之也会通知。这种方式的吞吐量也会高一些。
- 其次说一些RabbitMQ自己弄丢了消息
这种情况的解决方案可以将RabbitMQ设置为持久化
。除非有及其罕见的情况RabbitMQ还没来得及持久化自己就挂了,可能会导致少量的数据丢失,当然这种概率是很小的。
- 最后便是第三种情况:消费者丢失了消息
只有当你打开了消费者的autoAck
的这样一个机制:你消费到了数据之后消费者会自动通知RabbitMQ说我已经消费到了这条数据;这样会出现一种情况:假设你消费到了一条数据但是还没有处理完,此时消费者就自动autoAck
了。此时恰巧消费者系统服务挂了,消息还没来得及处理而且RabbitMQ以为该消息已经处理掉了。解决方案便是关掉RabbitMQ的自动ACK机制
面试官:不错,刚刚你有提到RabbitMQ设置持久化。你知道它怎么配置持久化吗:
派大星:直到的。具体步骤如下(注意两者缺一不可,需同时设置):
- 创建queue的时候将其设置为持久化的,这样保证RabbitMQ持久化queue的元数据,但是不会持久化queue里的数据
- 另外发送消息的时候将消息的
deliveryMode
设置为2,就是将消息设置为持久化。此时RabbitMQ就会将消息持久化到磁盘上去。
面试官:不错,但是我们这边实际工作中用的MQ是Kafka居多,关于Kafka消息丢失就以上情况你了解具体的解决方案吗?
派大星:这个也了解一些。
- 首先说一下。Kafka中消费者弄丢了消息的场景:
具体过程为消费者自动提交了offset,其实消息还没有处理完。和RabbitMQ情况差不多。解决方案为:就是关闭自动提交offset,手动提交offset
- 其次说一下Kafka弄丢了消息
主要表现形式为:Kafka的leader接收到了消息但是还没来得及同步给follwer就挂了,此时follwer变成了leader。导致数据丢失。解决方案为:需要设置4个参数:
- 给topic设置
replication.replicas
参数,这个值必须要大于1,也就是要求每个parttion只要有两个副本。 - 在Kafka服务端设置
min.insync.replicas
参数:这个值必须要大于1,这个是要求一个leader只要感知到最少有一个follwer还跟自己保持联系,这样才能确保leader还有一个follwer。 - 在producer段设置
ack=all
:这个要求是每条数据必须是写入所有的replica之后,才能认为是成功了。 - 在producer端设置
retries=MAX
:这个要求一旦写入失败,就无限重试,卡在这里。
按照上述配置后至少保证了在Kafka段在leader所在的broker发生故障进行leader切换时,数据不会丢失。
- 最后聊一下生产者丢失数据的情况
如果是按照上述方式配置了ack=all
则一定不会丢,要求是:你的leader接收到消息,所有的follwer都同步到了消息之后,才认为本次消息发送成功,否则生产者会重试无限次。