面试大杀器:消息中间件如何实现消费吞吐量的百倍优化?【石杉的架构笔记】

4,022 阅读12分钟

欢迎关注微信公众号:石杉的架构笔记(id:shishan100)

目录

(1)前请提示

(2)unack消息的积压问题

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

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

(5)低吞吐量问题

(6)合理设置prefetch count

(7)阶段性总结

1、前情提示

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

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

这篇文章,我们将会对ack底层的delivery tag机制进行更加深入的分析,让大家理解的更加透彻一些。

面试时,如果被问到消息中间件数据不丢失问题的时候,可以更深入到底层,给面试官进行分析。

2、unack消息的积压问题

首先,我们要给大家介绍一下RabbitMQ的prefetch count这个概念。

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

然后,我们进行ack时,也会带上这个delivery tag,基于同一个channel进行ack,ack消息里会带上delivery tag让RabbitMQ知道是对哪一次消息投递进行了ack,此时就可以对那条消息进行删除了。

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

所以大家可以考虑一下,对于每个channel而言(你就认为是针对每个消费者服务实例吧,比如一个仓储服务实例),其实都有一些处于unack状态的消息。

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

然后仓储服务接收到一条消息以后,要处理这条消息需要耗费时间,此时消息肯定是unack状态吧?

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

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

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

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

所以说,RabbitMQ是必须要考虑一下消费者服务的处理能力的。

大家看看下面的图,感受一下如果消费者服务实例的内存中积压消息过多,都是unack的状态,此时会怎么样。

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

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

你可以通过 “channel.basicQos(10)” 这个方法来设置当前channel的prefetch 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了,此时才能继续投递下一个消息。

老规矩,给大家上一张图,我们一起来看看这个东西是啥意思。

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

好!现在大家对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的消息会被重新投递给其他的消费者服务,此时其他消费者服务一样的情况,直接宕机,最后造成雪崩效应。

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

大家来看看下面的图,自己感受一下现场的氛围。

5、低吞吐量问题

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

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

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

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

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

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

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

大家看看下面的图,感受一下低吞吐量的现场。

6、合理的设置prefetch count

所以鉴于上面两种极端情况,RabbitMQ官方给出的建议是prefetch count一般设置在100~300之间。

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

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

但是其实在我们的实践中,这个prefetch count大家完全是可以自己去压测一下的。

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

7、阶段性总结

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

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

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

自动ack机制

delivery tag机制

ack批量与异步提交机制

消息重发机制

手动nack触发消息重发机制

prefetch count过大导致内存溢出问题

prefetch count过小导致吞吐量过低

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

所以到现在,单论消费端这块的数据不丢失技术方案,相信大家在面试的时候就可以有一整套自己的理解和方案可以阐述了。

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

end

如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!

一大波微服务、分布式、高并发、高可用的原创系列文章正在路上

欢迎扫描下方二维码,持续关注:

石杉的架构笔记(id:shishan100)

十余年BAT架构经验倾囊相授

推荐阅读:

1、拜托!面试请不要再问我Spring Cloud底层原理

2、【双11狂欢的背后】微服务注册中心如何承载大型系统的千万级访问?

3、【性能优化之道】每秒上万并发下的Spring Cloud参数优化实战

4、微服务架构如何保障双11狂欢下的99.99%高可用

5、兄弟,用大白话告诉你小白都能听懂的Hadoop架构原理

6、大规模集群下Hadoop NameNode如何承载每秒上千次的高并发访问

7、【性能优化的秘密】Hadoop如何将TB级大文件的上传性能优化上百倍

8、拜托,面试请不要再问我TCC分布式事务的实现原理!

9、【坑爹呀!】最终一致性分布式事务如何保障实际生产中99.99%高可用?

10、拜托,面试请不要再问我Redis分布式锁的实现原理!

11、【眼前一亮!】看Hadoop底层算法如何优雅的将大规模集群性能提升10倍以上?

12、亿级流量系统架构之如何支撑百亿级数据的存储与计算

13、亿级流量系统架构之如何设计高容错分布式计算系统

14、亿级流量系统架构之如何设计承载百亿流量的高性能架构

15、亿级流量系统架构之如何设计每秒十万查询的高并发架构

16、亿级流量系统架构之如何设计全链路99.99%高可用架构

17、七张图彻底讲清楚ZooKeeper分布式锁的实现原理

18、大白话聊聊Java并发面试问题之volatile到底是什么?

19、大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?

20、大白话聊聊Java并发面试问题之谈谈你对AQS的理解?

21、大白话聊聊Java并发面试问题之公平锁与非公平锁是啥?

22、大白话聊聊Java并发面试问题之微服务注册中心的读写锁优化

23、互联网公司的面试官是如何360°无死角考察候选人的?(上篇)

24、互联网公司面试官是如何360°无死角考察候选人的?(下篇)

25、Java进阶面试系列之一:哥们,你们的系统架构中为什么要引入消息中间件?

26、【Java进阶面试系列之二】:哥们,那你说说系统架构引入消息中间件有什么缺点?

27、【行走的Offer收割机】记一位朋友斩获BAT技术专家Offer的面试经历

28、【Java进阶面试系列之三】哥们,消息中间件在你们项目里是如何落地的?

29、【Java进阶面试系列之四】扎心!线上服务宕机时,如何保证数据100%不丢失?

30、一次JVM FullGC的背后,竟隐藏着惊心动魄的线上生产事故!

31、【高并发优化实践】10倍请求压力来袭,你的系统会被击垮吗?

32、【Java进阶面试系列之五】消息中间件集群崩溃,如何保证百万生产数据不丢失?

33、亿级流量系统架构之如何在上万并发场景下设计可扩展架构(上)?

34、亿级流量系统架构之如何在上万并发场景下设计可扩展架构(中)?

35、亿级流量系统架构之如何在上万并发场景下设计可扩展架构(下)?

36、亿级流量架构第二弹:你的系统真的无懈可击吗?

37、亿级流量系统架构之如何保证百亿流量下的数据一致性(上)

38、亿级流量系统架构之如何保证百亿流量下的数据一致性(中)?

39、亿级流量系统架构之如何保证百亿流量下的数据一致性(下)?

40、互联网面试必杀:如何保证消息中间件全链路数据100%不丢失(1)

41、互联网面试必杀:如何保证消息中间件全链路数据100%不丢失(2

作者:石杉的架构笔记 链接:juejin.im/post/684490… 来源:掘金 著作权归作者所有,转载请联系作者获得授权!