《2021最新Java面试题全集-2021年第二版》不断更新完善!

    

第十九章 Kafka

1:为什么要使用 Kafka,为什么要使用消息队列?

1)缓冲和削峰

    上游数据时有突发流量,下游可能扛不住,或者下游没有足够多的机器来保证冗余,Kafka在中间可以起到一个缓冲的作用,把消息暂存在Kafka中,下游服务就可以按照自己的节奏进行慢慢处理。

2)解耦和扩展性

项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。

3)冗余

可以采用一对多的方式,一个生产者发布消息,可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。

4)健壮性

消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。

5)可恢复性:

系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

6)顺序保证:

在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的, 并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性)

7)缓冲:

有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。

8)异步通信

很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

 

2:消息队列有什么缺点?

1)系统可用性降低:

系统引入的外部依赖越多,越容易挂掉,本来你就是A系统调用BCD三个系统的接口就好了, ABCD四个系统好好的,没啥问题,你偏加个MQ进来,万一MQ挂了咋整?MQ挂了,整套系统崩溃了,你不就完了么。

2)系统复杂性提高:

硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?

3)一致性问题:

A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了。

所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,最好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了10倍。但是关键时刻,用,还是得用的。。。

 

3:Kafka 可以脱离 Zookeeper 单独使用吗?为什么?

Kafka 不能脱离 Zookeeper 单独使用,因为 Kafka 使用 Zookeeper 管理和协调 Kafka 的节点服务器。

未来的Kafka可能会脱离ZK而单独使用,Kafka社区已经发起了去ZK的改造版本。

 

4:Kafka 判断一个节点是否还活着有那两个条件?

1)节点必须可以维护和 ZooKeeper 的连接, Zookeeper 通过心跳机制检查每个节点的连接

2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久

 

5:Kafka中的ISRAR又代表什么?ISR的伸缩又指什么

ISR:In-Sync Replicas 副本同步队列

AR:Assigned Replicas 所有副本ISR是由leader维护,followerleader同步数据有一些延迟,任意一个超过阈值都会把follower剔除出ISR, 存入OSROutof-Sync Replicas)列表,新加入的follower也会先存放在OSR中。

AR=ISR+OSR

 

6:什么情况下一个 broker 会从 isr中踢出去

leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护 ,如果一个follower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除

 

7:Kafka中的broker 是干什么的

    broker 是消息的代理,ProducersBrokers里面的指定Topic中写消息,ConsumersBrokers里面拉取指定Topic的消息,然后进行业务处理,broker在中间起到一个代理保存消息的中转站。

 

8:producer 是否直接将数据发送到broker leader(主节点)

    producer 直接将数据发送到 broker leader(主节点),不需要在多个节点进行分发,为了帮助 producer 做到这点,所有的 Kafka 节点都可以及时的告知:哪些节点是活动的,目标topic 目标分区的 leader 在哪。这样 producer 就可以直接将消息发送到目的地了

 

9:Kafa consumer 是否可以消费指定分区消息?

    Kafka consumer 消费消息时,向 broker 发出"fetch"请求去消费特定分区的消息, consumer指定消息在日志中的偏移量( offset),就可以消费从这个位置开始的消息, customer 拥有了 offset 的控制权,可以向后回滚去重新消费之前的消息,这是很有意义的

 

10:Kafka 消息是采用 Pull 模式,还是 Push 模式?

Kafka 最初考虑的问题是, customer 应该从 brokes 拉取消息还是 brokers 将消息推送到consumer,也就是 pull push

在这方面, Kafka 遵循了一种大部分消息系统共同的传统的设计:producer 将消息推送到 broker consumer broker 拉取消息。

push 模式有好处也有坏处:由 broker 决定消息推送的速率,对于不同消费速率的consumer 就不太好处理了。消息系统都致力于让 consumer 以最大的速率最快速的消费消息,但不幸的是, push 模式下,当 broker 推送的速率远大于 consumer 消费的速率时,consumer 恐怕就要崩溃了。最终 Kafka 还是选取了传统的 pull 模式。

Pull 模式的另外一个好处是 consumer 可以自主决定是否批量的从 broker 拉取数据。Push模式必须在不知道下游 consumer 消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。如果为了避免 consumer 崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。Pull 模式下, consumer 就可以根据自己的消费能力去决定这些策略。

Pull 模式有个缺点是,如果 broker 没有可供消费的消息,将导致 consumer 不断在循环中轮询,直到新消息到达。为了避免这点, Kafka 有个参数可以让 consumer 阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发)

 

11:Kafka 存储在硬盘上的消息格式是什么?

    消息由一个固定长度的头部和可变长度的字节数组组成。头部包含了一个版本号和 CRC32校验码。

1.  消息长度: 4 bytes (value: 1+4+n)

2.  版本号: 1 byte 3CRC

3.  校验码: 4 bytes

4.  具体的消息: n bytes

 

12:Kafka 高效文件存储设计特点:

1Kafka topic 中一个 parition 大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。

2)通过索引信息可以快速定位 message 和确定 response 的最大大小。

3)通过 index 元数据全部映射到 memory,可以避免 segment file I/O 磁盘操作。

4)通过索引文件稀疏存储,可以大幅降低 index 文件元数据占用空间大小。

 

13:Kafka 与传统消息系统之间有三个关键区别

1Kafka 持久化日志,这些日志可以被重复读取和无限期保留

2Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过复制数据提升容错能力和高可用性

3Kafka 支持实时的流式处理

 

14:Kafka 创建 Topic 时如何将分区放置到不同的 Broker

1)副本因子不能大于 Broker 的个数;

2)第一个分区(编号为 0)的第一个副本放置位置是随机从 brokerList 选择的;

3)其他分区的第一个副本放置位置相对于第 0 个分区依次往后移。也就是如果我们有 5 Broker 5 个分区,假设第一个分区放在第四个 Broker 上,那么第二个分区将会放在第五个 Broker 上;第三个分区将会放在第一个 Broker 上;第四个分区将会放在第二个Broker 上,依次类推;

4)剩余的副本相对于第一个副本放置位置其实是由 nextReplicaShift 决定的,而这个数也是随机产生的

 

 

 

15:Kafka 有几种数据保留的策略?

Kafka 有两种数据保存策略:按照过期时间保留和按照存储的消息大小保留

 

 

16:什么情况会导致 Kafka 运行变慢?

cpu 性能瓶颈

磁盘读写瓶颈

网络瓶颈

 

17:使用 Kafka 集群需要注意什么?

集群的数量不是越多越好,最好不要超过 7 个,因为节点越多,消息复制需要的时间就越长,整个群组的吞吐量就越低。

集群数量最好是单数,因为超过一半故障,集群就不能用了,设置为单数容错率更高

 

18:partition 的数据如何保存到硬盘

topic 中的多个 partition 以文件夹的形式保存到 broker,每个分区序号从 0 递增,且消息有序 Partition 文件下有多个 segment xxx.index xxx.log

segment 文件里的 大小和配置文件大小一致可以根据要求修改, 默认为 1g ,如果大小大于 1g 时,会滚动一个新的 segment 并且以上一个 segment 最后一条消息的偏移量命名

 

19:讲讲 Kafka 维护消费状态跟踪的方法

大部分消息系统在 broker 端的维护消息被消费的记录:一个消息被分发到consumer 后, broker 就马上进行标记或者等待 customer 的通知后进行标记。这样也可以在消息在消费后立马就删除以减少空间占用。

但是这样会不会有什么问题呢?如果一条消息发送出去之后就立即被标记为消费过的,一旦 consumer 处理消息时失败了(比如程序崩溃),消息就丢失了。

为了解决这个问题, 很多消息系统提供了另外一个个功能:当消息被发送出去之后仅仅被标记为已发送状态,当接到 consumer 已经消费成功的通知后才标记为已被消费的状态。

这虽然解决了消息丢失的问题,但产生了新问题,首先如果 consumer处理消息成功了,但是向 broker 发送响应时失败了,这条消息将被消费两次。第二个问题时,broker 必须维护每条消息的状态,并且每次都要先锁住消息然后更改状态然后释放锁。这样麻烦又来了,且不说要维护大量的状态数据,比如如果消息发送出去但没有收到消费成功的通知,这条消息将一直处于被锁定的状态,Kafka 采用了不同的策略。

Topic 被分成了若干分区,每个分区在同一时间只被一个 consumer 消费。这意味着每个分区被消费的消息在日志中的位置仅仅是一个简单的整数:o_set。这样就很容易标记每个分区消费状态就很容易了,仅仅需要一个整数而已。这样消费状态的跟踪就很简单了。

这带来了另外一个好处:consumer 可以把 o_set 调成一个较老的值,去重新消费老的消息。这对传统的消息系统来说看起来有些不可思议,但确实是非常有用的,谁规定了一条消息只能被消费一次呢?

 

20:消费者故障,出现活锁问题如何解决?

消费者出现活锁的情况,就是它持续的发送心跳,但是没有处理。

为了预防消费者在这种情况下一直持有分区,使用max.poll.interval.ms 活跃检测机制。 在此基础上,如果你调用 poll 的频率大于最大间隔,则客户端将主动地离开组,以便其他消费者接管该分区。

发生这种情况时,你会看到 offset 提交失败(调用 commitSync()引发的 CommitFailedException)。这是一种安全机制,保障 只有活动成员能够提交 offset。所以要留在组中,你必须持续调用 poll

消费者提供两个配置设置来控制 poll 循环:

1max.poll.interval.ms:增大 poll 的间隔,可以为消费者提供更多的时间去处理返回的消息(调用 poll(long)返回的消息,通常返回的消息都是一批)。缺点是此值越大将会延迟组重新平衡。

2max.poll.records:此设置限制每次调用 poll 返回的消息数,这样可以更容易的预测每次 poll 间隔要处理的最大值。通过调整此值,可以减少 poll 间隔,减少重新平衡分组的

对于消息处理时间不可预测地的情况,这些选项是不够的。 处理这种情况的推荐方法是将消息处理移到另一个线程中,让消费者继续调用 poll

但是必须注意确保已提交的 offset 不超过实际位置。另外,你必须禁用自动提交,并只有在线程完成处理后才为记录手动提交偏移量(取决于你)

还要注意,你需要 pause 暂停分区,不会从 poll 接收到新消息,让线程处理完之前返回的消息(如果你的处理能力比拉取消息的慢,那创建新线程将导致你机器内存溢出)

 

21:消息传输的事务定义?

(1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次都不传

(2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输.

(3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输 一次而且仅仅被传输一次,这是大家所期望的

 

22:Kafka ack 机制

request.required.acks 有三个值: 0 1 -1

0:生产者不会等待 broker ack,这个延迟最低但是存储的保证最弱,当 server 挂掉的时候就会丢数据

1:服务端会等待 ack 值,leader 副本确认接收到消息后发送 ack 但是如果 leader 挂掉后他不确保是否复制完成,新 leader 也会导致数据丢失

-1:同样在 1 的基础上,服务端会等所有的 follower 的副本收到数据后,才会收到 leader 发出的 ack,这样数据不会丢失

 

23:Kafka 的消费者如何消费数据

消费者每次消费数据的时候,消费者都会记录消费的物理偏移量( offset)的位置,等到下次消费时,他会接着上次位置继续消费

 

24:kafaka 生产数据时数据的分组策略

生产者决定数据产生到集群的哪个 partition 中,每一条消息都是以(keyvalue)格式, Key是由生产者发送数据传入, 所以生产者(key)决定了数据产生到集群的哪个 partition

 

25:如何保障Kafka中的消息不会丢失?

要确定Kafka的消息是否丢失,从两个方面分析入手:消息发送和消息消费。

1:消息发送

Kafka消息发送有两种方式:同步(sync)和异步(async),默认是同步方式,可通过producer.type属性进行配置。Kafka通过配置request.required.acks属性来确认消息的生产:0--表示不进行消息接收是否成功的确认;1--表示当Leader接收成功时确认;-1--表示LeaderFollower都接收成功时确认。

综上所述,有6种消息生产的情况,下面分情况来分析消息丢失的场景:

1acks=0,不和Kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;

2acks=1、同步模式下,只有Leader确认接收成功后但挂掉了,副本没有同步,数据可能丢失;

2:消息消费

Kafka消息消费有两个consumer接口,Low-level APIHigh-level APILow-level API:消费者自己维护offset等值,可以实现对Kafka的完全控制;High-level API:封装了对paritionoffset的管理,使用简单。

如果使用高级接口High-level API,可能存在一个问题就是当消息消费者从集群中把消息取出来、并提交了新的消息offset值后,还没来得及消费就挂掉了,那么下次再消费时之前没消费成功的消息就诡异的消失了。

解决办法:

1)针对消息丢失:同步模式下,确认机制设置为-1,即让消息写入LeaderFollower之后再确认消息发送成功;

2)异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态

 

26:如何保证消息不被重复消费?

这个主要是我们要保障 消费消息 的幂等性。

比如把消费过的消息ID存放到数据库或者是Redis,每次处理消息前,先根据消息ID查一下,如果消费过了,就不处理了。

这就需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后消费之后,先根据这个id去比如Redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个idRedis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

 

27:Kafka中如何保障顺序消费消息?

Kafka分布式的单位是 partitionKafka每个partition中的消息在写入时都是有序的。消费时,每个partition只能被每一个group中的一个消费者消费,保证了消费时也是有序的, 可以保证 FIFO 的顺序。

不同 partition 之间不能保证顺序。如果为了保证topic整个有序,那么一个简单的方案就是将partition调整为1

保障顺序消费消息的另外一个方案:

可以通过 message key 来定义,因为同一个 key message 可以保证只发送到同一个 partition

Kafka中发送 1 条消息的时候,可以指定(topic, partition, key) 3 个参数。 partiton key 是可选的。如果你指定了 partition,那就是所有消息发往同 1 partition,就是有序的。

并且在消费端,Kafka 保证,1 partition 只能被 1 consumer 消费。或者你指定 key(比如 order id),具有同 1 key 所有消息,会发往同 1 partition

 

28:Kafka中用到的mmap是什么?

Memory Mapped Files,简称mmap,简单描述其作用就是:将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。

它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。

通过mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),也不必关心内存的大小有虚拟内存为我们兜底。

使用这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。

mmap也有一个很明显的缺陷——不可靠,写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘。

Kafka提供了一个参数——producer.type来控制是不是主动flush;如果Kafka写入到mmap之后就立即flush然后再返回Producer叫同步(sync);写入mmap之后立即返回Producer不调用flush叫异步(async)

 

29:为什么Kafka这么快?

Kafka作为MQ也好,作为存储层也好,无非是两个重要功能,一是Producer生产的数据存到broker,二是 Consumerbroker读取数据;我们把它简化成如下两个过程:

1)网络数据持久化到磁盘 (Producer Broker)

2)磁盘文件通过网络发送(Broker Consumer

那为什么 “Kafka用了磁盘,还速度快

1:顺序读写

磁盘顺序读或写的速度400M/s,能够发挥磁盘最大的速度。

随机读写,磁盘速度慢的时候十几到几百K/s。这就看出了差距。

Kafka将来自Producer的数据,顺序追加在partitionpartition就是一个文件,以此实现顺序写入。

Consumerbroker读取数据时,因为自带了偏移量,接着上次读取的位置继续读,以此实现顺序读。

顺序读写,是Kafka利用磁盘特性的一个重要体现。

 

2:零拷贝 sendfile(in,out)

数据直接在内核完成输入和输出,不需要拷贝到用户空间再写出去。

Kafka数据写入磁盘前,数据先写到进程的内存空间。

传统方式实现:先读取磁盘、再用socket发送,实际也是进过四次copy

Linux 2.4+ 内核通过 sendfile 系统调用,提供了零拷贝。磁盘数据通过 DMA 拷贝到内核态Buffer后,直接通过DMA拷贝到 NIC Buffer(socket buffer),无需 CPU 拷贝。

这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件到 网络发送由一个 sendfile 调用完成,整个过程只有两次上下文切换,因此大大提高了性能。

 

3mmap文件映射

虚拟映射只支持文件;在进程的非堆内存开辟一块内存空间,和OS内核空间的一块内存进行映射。

Kafka数据写入、是写入这块内存空间,但实际这块内存和OS内核内存有映射,也就是相当于写在内核内存空间了,且这块内核空间、内核直接能够访问到,直接落入磁盘。

这里,我们需要清楚的是:内核缓冲区的数据,flush就能完成落盘。

总的来说Kafka快的原因:

1partition顺序读写,充分利用磁盘特性,这是基础;

2Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入;

3Customerbroker读取数据,采用sendfile,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送。

 

30:为什么Kafka不支持读写分离?

Kafka 中,生产者写入消息、消费者读取消息的操作都是与 leader 副本进行交互的,从而实现的是一种主写主读的生产消费模型。

Kafka 并不支持主写从读,因为主写从读有 2 个很明显的缺点:

1:数据一致性问题

数据从主节点转到从节点必然会有一个延时的时间窗口,这个时间窗口会导致主从节点之间的数据不一致。

某一时刻,在主节点和从节点中 A 数据的值都为 X 之后将主节点中 A 的值修改为 Y,那么在这个变更通知到从节点之前,应用读取从节点中的 A 数据的值并不为最新的 Y,由此便产生了数据不一致的问题。

2:延时问题

类似 Redis 这种组件,数据从写入主节点到同步至从节点中的过程需要经 历网络主节点内存网络从节点内存这几个阶段,整个过程会耗费一定的时间。

而在 Kafka 中,主从同步会比 Redis 更加耗时,它需要经历网络主节点内存主节点磁盘网络从节点内存从节点磁盘这几个阶段。对延时敏感的应用而言,主写从读的功能并不太适用。

 

 

31:Kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 Kafka 将如何处理?

这个时候 Kafka 会执行数据清除工作,时间和大小不论那个满足条件,都会清空数据。

 

32:Kafka中到底是怎么推进时间的?

Kafka中的定时器借助了JDK中的DelayQueue来协助推进时间轮。

具体做法是对于每个使用到的TimerTaskList都会加入到DelayQueue中。Kafka中的TimingWheel专门用来执行插入和删除TimerTaskEntry的操作,而DelayQueue专门负责时间推进的任务。

再试想一下,DelayQueue中的第一个超时任务列表的expiration200ms,第二个超时任务为840ms,这里获取DelayQueue的队头只需要O(1)的时间复杂度。

如果采用每秒定时推进,那么获取到第一个超时的任务列表时执行的200次推进中有199次属于空推进,而获取到第二个超时任务时有需要执行639空推进,这样会无故空耗机器的性能资源,这里采用DelayQueue来辅助以少量空间换时间,从而做到了精准推进

Kafka中的定时器真可谓是知人善用,用TimingWheel做最擅长的任务添加和删除操作,而用DelayQueue做最擅长的时间推进工作,相辅相成。

 

33:Kafka如何实现延迟队列?

Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定时器(SystemTimer)。

JDKTimerDelayQueue插入和删除操作的平均时间复杂度为O(nlog(n)),并不能满足Kafka的高性能要求,而基于时间轮可以将插入和删除操作的时间复杂度都降为O(1)

时间轮的应用并非Kafka独有,其应用场景还有很多,在NettyAkkaQuartzZookeeper等组件中都存在时间轮的踪影。底层使用数组实现,数组中的每个元素可以存放一个TimerTaskList对象。TimerTaskList是一个环形双向链表,在其中的链表项TimerTaskEntry中封装了真正的定时任务TimerTask

Kafka中到底是怎么推进时间的呢?Kafka中的定时器借助了JDK中的DelayQueue来协助推进时间轮。具体做法是对于每个使用到的TimerTaskList都会加入到DelayQueue中。Kafka中的TimingWheel专门用来执行插入和删除TimerTaskEntry的操作,而DelayQueue专门负责时间推进的任务。

试想一下,DelayQueue中的第一个超时任务列表的expiration200ms,第二个超时任务为840ms,这里获取DelayQueue的队头只需要O(1)的时间复杂度。如果采用每秒定时推进,那么获取到第一个超时的任务列表时执行的200次推进中有199次属于空推进,而获取到第二个超时任务时有需要执行639空推进,这样会无故空耗机器的性能资源,这里采用DelayQueue来辅助以少量空间换时间,从而做到了精准推进

Kafka中的定时器真可谓是知人善用,用TimingWheel做最擅长的任务添加和删除操作,而用DelayQueue做最擅长的时间推进工作,相辅相成。

 

34:如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?

假设现在消费端出故障了,大量消息在mq里积压:

1:几千万条数据在MQ里积压了几个小时了,怎么处理呢?

假定一个消费者一秒消费1000条,一秒3个消费者是消费3000条,一分钟是18万条。几千万条,即使消费者恢复了,也需要大概1小时的时间才能恢复过来

一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下:

1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉

2)新建一个topicpartition是原来的10倍,临时建立好原先10倍或者20倍的queue数量

3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue

4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据

5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据

6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息

2:第二种情况,就是消息大量积压的时候,就人为直接丢弃数据,让系统先正常运行。

然后等过了高峰期以后,这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。

假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到MQ里去再补一次

 3:如果消息积压在MQ里,很长时间都没处理掉,此时导致mq都快写满了,咋办?

这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。

 

35:如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路

其实聊到这个问题,一般面试官要考察两块:

1)你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个MQ的架构原理

2)看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来

碰到这样的面试题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西。类似的问题,我经常问的还有,如果让你来设计一个spring框架你会怎么做?如果让你来设计一个dubbo框架你会怎么做?如果让你来设计一个mybatis框架你会怎么做?

其实回答这类问题,说白了,起码不求你看过那技术的源码,起码你大概知道那个技术的基本原理,核心组成部分,基本架构构成,然后参照一些开源的技术把一个系统设计出来的思路说一下就好

比如说这个消息队列系统,我们来从以下几个角度来考虑一下

1)首先这个mq得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下Kafka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?

2)其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘,才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是Kafka的思路。

3)其次你考虑一下你的mq的可用性啊?这个事儿,可以想想 Kafka的高可用保障机制。多副本 -> leader & follower -> broker挂了重新选举leader即可对外服务。

4)能不能支持数据0丢失啊?可以的,参考前面的如何保障消息不丢失

一个mq肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。