互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第三篇

前情提示

上一篇文章:<<互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第二篇>>,咱们分析了 ack 机制的底层实现原理(delivery tag机制),还有消除处理失败时的nack机制如何触发消息重发。html

经过这个,已经让你们进一步对消费端保证数据不丢失的方案的理解更进一层了。面试

这篇文章,咱们将会对 ack 底层的delivery tag机制进行更加深刻的分析,让你们理解的更加透彻一些。微信

面试时,若是被问到消息中间件数据不丢失问题的时候,能够更深刻到底层,给面试官进行分析。架构

unack消息的积压问题

首先,咱们要给你们介绍一下RabbitMQ的prefetch count这个概念。并发

你们看过上篇文章以后应该都知道了,对每一个 channel(其实对应了一个消费者服务实例,你大致能够这么来认为),RabbitMQ 投递消息的时候,都是会带上本次消息投递的一个delivery tag的,惟一标识一次消息投递。异步

而后,咱们进行 ack 时,也会带上这个 delivery tag,基于同一个 channel 进行 ack,ack 消息里会带上 delivery tag 让RabbitMQ知道是对哪一次消息投递进行了 ack,此时就能够对那条消息进行删除了。高并发

你们先来看一张图,帮助你们回忆一下这个delivery tag的概念。fetch

因此你们能够考虑一下,对于每一个channel而言(你就认为是针对每一个消费者服务实例吧,好比一个仓储服务实例),其实都有一些处于unack状态的消息。3d

好比RabbitMQ正在投递一条消息到channel,此时消息确定是unack状态吧?code

而后仓储服务接收到一条消息之后,要处理这条消息须要耗费时间,此时消息确定是unack状态吧?

同时,即便你执行了ack以后,你要知道这个ack他默认是异步执行的,尤为若是你开启了批量ack的话,更是有一个延迟时间才会ack的,此时消息也是unack吧?

那么你们考虑一下,RabbitMQ 他可以无限制的不停给你的消费者服务实例推送消息吗?

明显是不能的,若是 RabbitMQ 给你的消费者服务实例推送的消息过多过快,好比都有几千条消息积压在某个消费者服务实例的内存中。

那么此时这几千条消息都是unack的状态,一直积压着,是否是有可能会致使消费者服务实例的内存溢出?内存消耗过大?甚至内存泄露之类的问题产生?

因此说,RabbitMQ 是必需要考虑一下消费者服务的处理能力的。

你们看看下面的图,感觉一下若是消费者服务实例的内存中积压消息过多,都是unack的状态,此时会怎么样。

如何解决unack消息的积压问题

正是由于这个缘由,RabbitMQ基于一个prefetch count来控制这个unack message的数量。

你能够经过“channel.basicQos(10)”这个方法来设置当前channelprefetch count

举个例子,好比你要是设置为10的话,那么意味着当前这个channel里,unack message的数量不能超过10个,以此来避免消费者服务实例积压unack message过多。

这样的话,就意味着RabbitMQ正在投递到channel过程当中的unack message,以及消费者服务在处理中的unack message,以及异步ack以后还没完成 ack 的unack message,全部这些message 加起来,一个 channel 也不能超过10个。

若是你要简单粗浅的理解的话,也大体能够理解为这个prefetch count就表明了一个消费者服务同时最多能够获取多少个 message 来处理。因此这里也点出了 prefetch 这个单词的意思。

prefetch 就是预抓取的意思,就意味着你的消费者服务实例预抓取多少条 message 过来处理,可是最多只能同时处理这么多消息。

若是一个 channel 里的unack message超过了prefetch count指定的数量,此时RabbitMQ就会中止给这个 channel 投递消息了,必需要等待已经投递过去的消息被 ack 了,此时才能继续投递下一个消息。

老规矩,给你们上一张图,咱们一块儿来看看这个东西是啥意思。

高并发场景下的内存溢出问题

好!如今你们对 ack 机制底层的另一个核心机制:prefetch 机制也有了一个深入的理解了。

此时,我们就应该来考虑一个问题了。就是如何来设置这个prefetch count呢?这个东西设置的过大或者太小有什么影响呢?

其实你们理解了上面的图就很好理解这个问题了。

假如说咱们把 prefetch count 设置的很大,好比说3000,5000,甚至100000,就这样特别大的值,那么此时会如何呢?

这个时候,在高并发大流量的场景下,可能就会致使消费者服务的内存被快速的消耗掉。

由于假如说如今MQ接收到的流量特别的大,每秒都上千条消息,并且此时你的消费者服务的prefetch count还设置的特别大,就会致使可能一瞬间你的消费者服务接收到了达到prefetch count指定数量的消息。

打个比方,好比一会儿你的消费者服务内存里积压了10万条消息,都是unack的状态,反正你的prefetch count设置的是10万。

那么对一个channel,RabbitMQ就会最多容忍10万个unack状态的消息,在高并发下也就最多可能积压10万条消息在消费者服务的内存里。

那么此时致使的结果,就是消费者服务直接被击垮了,内存溢出,OOM,服务宕机,而后大量unack的消息会被从新投递给其余的消费者服务,此时其余消费者服务同样的状况,直接宕机,最后形成雪崩效应。

全部的消费者服务由于扛不住这么大的数据量,所有宕机。

你们来看看下面的图,本身感觉一下现场的氛围。

低吞吐量问题

那么若是反过来呢,咱们要是把prefetch count设置的很小会如何呢?

好比说咱们把 prefetch count 设置为1?此时就必然会致使消费者服务的吞吐量极低。由于你即便处理完一条消息,执行ack了也是异步的。

给你举个例子,假如说你的 prefetch count = 1,RabbitMQ最多投递给你1条消息处于 unack 状态。

此时好比你刚处理完这条消息,而后执行了 ack 的那行代码,结果不幸的是,ack须要异步执行,也就是须要100ms以后才会让RabbitMQ感知到。

那么100ms以后RabbitMQ感知到消息被ack了,此时才会投递给你下一条消息!

这就尴尬了,在这100ms期间,你的消费者服务是否是啥都没干啊?

这不就直接致使了你的消费者服务处理消息的吞吐量可能降低10倍,甚至百倍,千倍,都有这种可能!

你们看看下面的图,感觉一下低吞吐量的现场。

合理的设置prefetch count

因此鉴于上面两种极端状况,RabbitMQ官方给出的建议是prefetch count通常设置在100~300之间。

也就是一个消费者服务最多接收到100~300个message来处理,容许处于unack状态。

这个状态下能够兼顾吞吐量也很高,同时也不容易形成内存溢出的问题。

可是其实在咱们的实践中,这个prefetch count你们彻底是能够本身去压测一下的。

好比说慢慢调节这个值,不断加大,观察高并发大流量之下,吞吐量是否愈来愈大,并且观察消费者服务的内存消耗,会不会OOM、频繁FullGC等问题。

阶段性总结

其实经过最近几篇文章,基本上已经把消息中间件的消费端如何保证数据不丢失这个问题剖析的较为深刻和透彻了。

若是你是基于RabbitMQ来作消息中间件的话,消费端的代码里,必须考虑三个问题:手动ack、处理失败的nack、prefetch count的合理设置

这三个问题背后涉及到了各类机制:

  • 自动ack机制
  • delivery tag机制
  • ack批量与异步提交机制
  • 消息重发机制
  • 手动nack触发消息重发机制
  • prefetch count过大致使内存溢出问题
  • prefetch count太小致使吞吐量太低

这些底层机制和问题,我们都一步步分析清楚了。

因此到如今,单论消费端这块的数据不丢失技术方案,相信你们在面试的时候就能够有一整套本身的理解和方案能够阐述了。

接下来下篇文章开始,咱们就来具体聊一聊:消息中间件的生产端如何保证数据不丢失。

互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第一篇
互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第二篇
互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第四篇

来源:【微信公众号 - 石杉的架构笔记】

相关文章
相关标签/搜索