|
中国石山老师最新力作:
《21天互联网Java高级面试训练营》
(分布式)
彻底解决Java工程师面试一线互联网公司的各种痛点
扫描下方二维码即可收听课程:
(详细课程目录见文末)
1、为什么要使用消息队列?
分析:一个使用消息队列的人却不知道自己为什么使用它,这就有点尴尬了。不回顾这一点,很容易糊涂,开始胡言乱语。
答:对于这个问题,我们只回答最重要的三个应用场景(不可否认还有其他,但只回答主要的三个),即以下六个字:解耦、异步、削峰
(1)解耦
传统模式:
传统模式的缺点:
中间件模式:
中间件模式的优点:
(2)异步
传统模式:
传统模式的缺点:
中间件模式:
中间件模式的优点:
(3)削峰
传统模式
传统模式的缺点:
中间件模式:
中间件模式的优点:
2、使用消息队列有什么缺点?
分析:对于一个使用MQ的项目,如果不考虑这个问题就引入MQ,会给项目带来风险。
当我们引进一项技术时,我们必须充分了解这项技术的缺点,以便防患于未然。记住,不要给公司挖坑!
答:答案也很简单。从以下两个角度来回答:
然而,我们仍然需要使用它。
3、如何选择消息队列?
首先博主只了解Kafka,对其他MQ没有了解,所以只能根据这四个MQ给出答案。
分析:由于项目中使用了MQ,所以需要提前对业界流行的MQ进行研究。如果你连各个MQ的优缺点都不了解,你可以根据自己的喜好使用某个MQ,或者针对项目进行挖掘。坑。
如果面试官问:“你为什么用这种MQ?”你直接回答“领导决定的”。这样的回答是非常LOW的。
再次强调,不要给公司挖坑。
我们可以看到版本发布更加频繁。至于卡夫卡,我就不给你们看了。简而言之,它更加活跃。详细情况你可以自己查一下。
这是另一个性能比较表
根据以上材料,可以得出以下两点:
(一)推荐中小型软件企业。
一方面,该语言天生具有高并发的特点,其管理界面使用起来非常方便。
俗话说,萧何也是成功者,萧何也是失败者!它的缺点也在这里。虽然是开源的,但是国内有多少程序员能够定制开发呢?
幸运的是,社区非常活跃,可以解决开发过程中遇到的bug,这对于中小型公司来说非常重要。
之所以不考虑Kafka,一方面是中小型软件公司不如互联网公司,数据量没有那么大。在选择消息中间件时,应该优先选择功能比较齐全的,所以Kafka被排除在外。
不考虑的原因是它是阿里巴巴出品的。如果阿里巴巴放弃维护,中小型公司一般抽不出人手进行定制开发,所以不推荐。
(2)对于大型软件公司,根据具体用途选择Kafka和Kafka。
一方面,大型软件公司有足够的资金来构建分布式环境和足够大的数据量。
大型软件公司还可以调配人力进行定制开发。毕竟国内有能力改JAVA源码的人还是不少的。
至于Kafka,根据业务场景,如果有日志收集功能,Kafka肯定是首选。选择哪一种取决于使用场景。
4、如何保证消息队列高可用?
分析:第二点提到,引入消息队列后,系统的可用性下降。在生产中,没有人以独立模式使用消息队列。
因此,作为一名合格的程序员,应该对消息队列的高可用有深入的了解。
如果在面试的时候,面试官问,你们的消息中间件是如何保证高可用的?
如果你的回答只是表明你只订阅和发布消息,面试官会怀疑你是不是只是玩玩,从来没有在生产中使用过。
因此,请做一名热爱思考、懂得思考、懂思考的程序员。
答:这个问题其实需要深入了解消息队列的集群模式才可以回答。
比如他的集群有多种模式,多slave异步复制模式,多slave同步双写模式。
多多slave模式部署架构图(网上找的,懒得画了):
其实博主第一次看到这张图的时候,觉得和Kafka类似,只不过用的是集群,而不是Kafka中的集群,用于保存、发现和从属。
通信流程如下:
与集群中的一个节点(随机选择)建立长连接,定期从中获取Topic路由信息,与Topic服务的提供者建立长连接,并定期向其发送心跳。
它只能发送消息给,但它是不同的。它还与提供Topic服务的Slave建立长连接。它可以订阅来自 Slave 或 Slave 的消息。
至于kafka,为了对比和说明,我直接展示kafka的拓扑架构图(我也找到了,懒得画了)
如上图所示,一个典型的Kafka集群包含几个(可以是Web前端生成的Page View,或者服务器日志、系统CPU等)、几个(Kafka支持水平扩展,一般数量越多,集群吞吐率越高),多个Group,一个集群。
Kafka 管理集群配置、选举以及何时发生组更改。
使用推送模式发布消息,使用拉取模式订阅和消费消息。
至于,也有普通集群和镜像集群模式。自己理解还是比较简单的。两个小时内你就能理解它们。
要求在回答高可用问题时,能够逻辑清晰地画出自己的MQ集群架构或者描述清楚。
5、如何保证消息不被重复消费?
分析:这个问题的另一个问题是,如何保证消息队列的幂等性?
这个问题可以认为是消息队列领域的一个基本问题。换句话说,就是考验你的设计能力。这个问题的答案可以根据具体的业务场景来回答。没有固定的答案。
答:我们先来说说为什么会出现重复消费?
其实不管是哪一种消息队列,重复消费的原因其实都是类似的。
正常情况下,当消费者消费一条消息时,消费完成后,会向消息队列发送一条确认消息。消息队列会知道该消息已被消费,并将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同。
例如,发送ACK确认消息返回成功标志。卡夫卡实际上有一个概念
简单来说(如果还是不懂,就出去找一篇Kafka从入门到精通的教程),每条消息都有一个。 Kafka消费完消息后,需要提交,让消息队列知道它已经被消费了。
重复消费的原因是什么?
由于网络传输等原因导致确认信息没有传输到消息队列中。这样一来,消息队列并不知道自己已经消费了该消息,并再次将该消息分发给其他消费者。
如何解决这个问题呢?这个问题根据业务场景分为以下几点。
(1) 例如,您收到此消息以执行数据库操作。
那很容易。给该消息一个唯一的主键。那么即使有重复消费,也会导致主键冲突,避免数据库出现脏数据。
(2)再比如,你收到这个消息,然后进行redis set操作。
这很容易,不需要解决它。因为无论设置多少次结果都是相同的,因此设置操作本质上是幂等的。
(3)如果以上两种情况还不行,就使用大招。
准备第三方介质来记录消耗情况。以redis为例,给消息分配一个全局ID。只要消息被消费,就会以KV的形式写入redis。消费者开始消费之前,可以先检查redis中是否有消费记录。
6、如何保证消费的可靠传输?
分析:在使用消息队列的过程中,要保证消息不能多或少被消费。如果不能实现可靠传输,可能会给公司造成上千万的财产损失。
同样,如果在使用过程中不考虑可靠传输,这不是给公司挖坑吗?你可以走开,公司损失的钱谁来承担。
再次强调,认真对待每一个项目,不要给公司挖坑。
答:其实这个可靠传输对于每个MQ都要从三个角度来分析:生产者丢失数据、消息队列丢失数据、消费者丢失数据。
(1)生产者丢失数据
从生产者丢失数据的角度出发,提供了模式来保证生产者不丢失消息。
该机制是指在发送消息之前,先打开事务(.()),然后再发送消息。如果发送过程中出现任何异常,交易将会回滚(.())。如果消息发送成功,交易将被提交(.())。
但缺点是吞吐量降低。因此,根据博主的经验,大多数模型都用于生产。
进入模式后,频道上发布的所有消息都将被分配一个唯一的 ID(从 1 开始)
一旦消息被传递到所有匹配的队列,就会向生产者发送一个 Ack(包含消息的唯一 ID)
这使得生产者知道消息已经正确到达目标队列。如果消息无法处理,则会向您发送 Nack 消息,您可以重试该操作。
处理Ack和Nack的代码如下(我说不是代码,但我偷偷做了):
<p><pre style="font-size: 16px;color: rgb(62, 62, 62);line-height: inherit;text-align: start;background-color: rgb(255, 255, 255);"> <code class="hljs java" style="margin-right: 2px;margin-left: 2px;padding: 0.5em;font-size: 14px;color: rgb(169, 183, 198);line-height: 18px;border-radius: 0px;background: rgb(40, 43, 46);display: block;font-family: Consolas, Inconsolata, Courier, monospace;overflow-x: auto;letter-spacing: 0px;word-wrap: normal !important;word-break: normal !important;overflow-y: auto !important;">channel.addConfirmListener(<span class="hljs-keyword" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">new</span> ConfirmListener() {<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> <span class="hljs-meta" style="font-size: inherit;color: rgb(91, 218, 237);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">@Override</span><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> <span class="hljs-function" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;"><span class="hljs-keyword" style="font-size: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">public</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">void</span> <span class="hljs-title" style="font-size: inherit;color: rgb(165, 218, 45);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">handleNack</span><span class="hljs-params" style="font-size: inherit;color: rgb(255, 152, 35);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">(<span class="hljs-keyword" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">long</span> deliveryTag, <span class="hljs-keyword" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">boolean</span> multiple)</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">throws</span> IOException </span>{<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> System.out.println(<span class="hljs-string" style="font-size: inherit;color: rgb(238, 220, 112);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">"nack: deliveryTag = "</span>+deliveryTag+<span class="hljs-string" style="font-size: inherit;color: rgb(238, 220, 112);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">" multiple: "</span>+multiple);<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> }<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> <span class="hljs-meta" style="font-size: inherit;color: rgb(91, 218, 237);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">@Override</span><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> <span class="hljs-function" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;"><span class="hljs-keyword" style="font-size: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">public</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">void</span> <span class="hljs-title" style="font-size: inherit;color: rgb(165, 218, 45);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">handleAck</span><span class="hljs-params" style="font-size: inherit;color: rgb(255, 152, 35);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">(<span class="hljs-keyword" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">long</span> deliveryTag, <span class="hljs-keyword" style="font-size: inherit;color: rgb(248, 35, 117);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">boolean</span> multiple)</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">throws</span> IOException </span>{<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> System.out.println(<span class="hljs-string" style="font-size: inherit;color: rgb(238, 220, 112);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">"ack: deliveryTag = "</span>+deliveryTag+<span class="hljs-string" style="font-size: inherit;color: rgb(238, 220, 112);line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;">" multiple: "</span>+multiple);<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /> }<br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" /><br style="font-size: inherit;color: inherit;line-height: inherit;word-wrap: inherit !important;word-break: inherit !important;" />});</code></pre></p>
(2)消息队列丢失数据
为了处理消息队列丢失数据的情况,通常是启用持久磁盘的配置。
此持久性配置可以与该机制结合使用。消息持久化到磁盘后,您可以向生产者发送 Ack 信号。
这样,如果消息在持久化到磁盘之前就死掉了,生产者将不会收到 Ack 信号,生产者会自动重新发送。
那么如何让它持久呢?顺便说一句,这实际上很容易。只需以下两步即可。
1. 将队列的持久化标志设置为true,表示它是一个持久化队列。
2.发送消息时,会=2
设置好之后,即使挂了,重启后数据也能恢复。
(3)消费者丢失数据
消费者丢失数据一般是因为使用自动确认消息模式。
在这种模式下,消费者自动确认收到消息。此时,该消息将被立即删除。在这种情况下,如果消费者遇到异常,无法处理消息,那么消息就会丢失。
至于解决办法,只需手动确认消息即可。
卡夫卡
向某人发布消息时,首先找到
然后不管Topic有多少个(即有多少个),只向该Topic发送消息。
该消息将写入其本地日志。每个都从中提取数据。
基于以上情况,得出以下分析
(1)生产者丢失数据
kafka生产中,基本上有一个和多个。信息将同步。
因此,为了防止生产者丢失数据,进行如下两个配置:
第一个配置是在最后设置acks=all。此配置保证只有同步完成后才认为消息发送成功。
最后设置=MAX,一旦写入失败,就会无限重试
(2)消息队列丢失数据
对于消息队列丢失数据的情况,无非是数据还没同步就挂掉了。此时会切换到其他消息,数据就会丢失。
对于这种情况,应该进行两种配置。
.参数,这个值必须大于1,即每个必须至少有2份
min..参数,这个值必须大于1。这就要求至少感知到至少有一个人还在和他接触。
这两个配置结合上面的生产者配置基本上可以保证Kafka不丢失数据。
(3)消费者丢失数据
在这种情况下,通常会自动提交,然后在处理程序时挂起。卡夫卡认为你已经处理好了。
再说一遍,它是用来做什么的?
:指Kafka主题中每个消费者组消费的下标。
简单来说,一条消息对应一个下标。如果每次消费数据时都提交,则下次消费将从提交的加一开始。
比如一个topic有100条数据,我消费了50条并提交。那么此时提交的kafka服务器记录是49(从0开始),所以下次消费将从50开始。
解决办法也很简单,改成手动提交即可。
和
请您自行查看
7. 如何保证消息的顺序?
分析:其实并不是所有的企业都有这个业务需求,但是这个问题我们还是需要重新审视。
答:针对这个问题,通过一定的算法,将需要保持顺序的消息放到同一个消息队列(即Kafka中的队列)中。然后只使用一个来消费queue。
有人可能会问:如果有多个消费者为了吞吐量而消费怎么办?
这个问题没有固定的答案。比如我们有一个微博操作,包括发微博、写评论、删除微博。这是三个异步操作。如果是这样的业务场景,那就再试一次吧。
例如,如果你作为消费者,首先执行写评论的操作,但此时微博还没有发布,那么写评论肯定失败了。稍等一下。等待另一个消费者先执行写评论的操作,然后再执行,就会成功。
总之,关于这个问题,我的观点是保证进入队列是有序的,出队列后的顺序交给消费者来保证。没有固定的作息规律。
总结
至此,希望读者在充分准备好本文提出的问题后,能够涵盖关于消息队列的大部分知识点。
如果面试官不问这些问题怎么办?这很简单。清楚地解释问题并在下面强调您考虑因素的全面性。
最后,我其实并不提倡这种突击审查。希望大家都能掌握基本技能,成为一名爱思考、懂得思考、会思考的程序员。
结尾 |
|