找回密码
 立即注册
搜索
查看: 59|回复: 0

中华石杉老师力作:21 天互联网 Java 进阶面试训练营解决面试痛点

[复制链接]

9420

主题

0

回帖

2万

积分

管理员

积分
28470
发表于 2024-11-23 23:50:15 | 显示全部楼层 |阅读模式
中国石山老师最新力作:

《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。

有人可能会问:如果有多个消费者为了吞吐量而消费怎么办?

这个问题没有固定的答案。比如我们有一个微博操作,包括发微博、写评论、删除微博。这是三个异步操作。如果是这样的业务场景,那就再试一次吧。

例如,如果你作为消费者,首先执行写评论的操作,但此时微博还没有发布,那么写评论肯定失败了。稍等一下。等待另一个消费者先执行写评论的操作,然后再执行,就会成功。

总之,关于这个问题,我的观点是保证进入队列是有序的,出队列后的顺序交给消费者来保证。没有固定的作息规律。

总结

至此,希望读者在充分准备好本文提出的问题后,能够涵盖关于消息队列的大部分知识点。

如果面试官不问这些问题怎么办?这很简单。清楚地解释问题并在下面强调您考虑因素的全面性。

最后,我其实并不提倡这种突击审查。希望大家都能掌握基本技能,成为一名爱思考、懂得思考、会思考的程序员。

结尾
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|【科创星域】 ( 京ICP备20013102号-15 )

GMT+8, 2025-5-5 20:04 , Processed in 0.597793 second(s), 20 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表