11.Kafka面试题

241 阅读16分钟

1. 为什么要使用消息队列?

  1. 解耦
  • 一个服务未来可能会添加许多不同的功能,比如积分和短信服务,这时候可以使用消息队列进行服务间解耦,上游服务只需要将消息放在消息队列中,下游的服务可以直接根据需要订阅消息队列即可。
  1. 异步
  • 例如刚开始的电商项目,可以就是粗暴的扣库存、下单。慢慢地又加上积分服务、短信服务等。如果所有的流程全部都是同步调用,系统整体响应时间会变得非常长。 调用链路长、响应就慢了,并且相对于扣库存和下单,积分和短信没必要这么的 "及时"。因此只需要在下单结束那个流程,扔个消息到消息队列中就可以直接返回响应了。而且积分服务和短信服务可以并行的消费这条消息。可以看出消息队列可以减少请求的等待,还能让服务异步并发处理,提升系统总体性能。
  1. 削峰填谷
  • 在有些时候,服务的请求数会激增,比如说双十一的时候,这时候我们的后端服务一下顶不住这么大的流量,这时候,可以将消息放在消息队列里面,下游服务尽全力消费即可。

2. 消息队列有哪两种模式?

  1. 点对点模式
  • 消费者主动拉取数据,收到消息后删除消息
  1. 发布订阅模式
  • 可以有多个topic主题(浏览、点赞、收藏、评论等)
  • 消费者消费数据之后,不删除数据
  • 每个消费者相互独立,都可以消费到数据

3. kafka中的 ISR 、OSR 和 AR 是什么?

ISR:与leader保持同步的follower集合

OSR:速率和leader相差大于10秒的follower集合(不同步的集合)

AR:分区的所有副本

  • 当ack=-1时,kafka全部完成同步才发送ack: 设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?

  • Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。Leader发生故障之后,就会从ISR中选举新的leader。

4. kafka的数据一致性如何保证? HW 和 LEO 是什么?

在这里插入图片描述

  1. follower故障

follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始向leader进行同步。等该follower的LEO大于等于该Partition的HW,即follower追上leader之后,就可以重新加入ISR了。

  1. leader故障

leader发生故障之后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。 在这里插入图片描述

注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。

5. kafka如何保证消息不丢失?

保证消息不丢失需要从三个阶段入手:

  1. Producer:

  • 生产者发送消息至 Broker ,需要处理 Broker 的响应,不论是同步还是异步发送消息,同步和异步回 调都需要做好 try-catch ,妥善的处理响应,如果 Broker 返回写入失败等错误消息,需要重试发送。 当多次发送失败需要作报警,日志记录等。

  • 生产者(Producer) 调用send方法发送消息之后,消息可能因为网络问题并没有发送过去。Kafka提供了同步发送消息方法,会返回一个Future对象,调用get()方法进行阻塞等待,就可以知道消息是否发送成功。如果消息发送失败的话,可以通过Producer 的retries(重试次数)参数进行设置,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显

  • 这样就能保证在生产消息阶段消息不会丢失。

  1. Broker

  • 当消息发送到了分区的leader副本,leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。

  • 解决办法就是将producer设置 acks = all。

  • ack = 0, 生产者在成功写入消息之前不会等待任何来自服务器的相应,就是如果broker没有收到消息,生产者也是无法得知。

    acks =1,代表我们的消息被leader副本接收之后就算被成功发送。

    acks = all,代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。

  1. Consumer

消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。

当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。

可以通过enable.auto.commit设置为false,关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset 。

6. kafka如何处理重复消息?

消息重复主要有两个方面的原因:

一般情况下,我们为了保证消息的可靠性,我们会开启消息确认机制

  1. 生产者
  • 当生产者将消息发给Broker,这时在等待Broker的响应时,可能存在 Broker 已经写入了,当时响应由于网络原因生产者没有收到,然后生产者又重发了一次,此时消息就重复了。
  1. 消费者
  • 消费者使用自动动提交offset模式,消费者收到消息还没消费时,提交offset并开始消费消息,结果网络异常,offset提交失败、消费消息成功

解决方案:

  • 对正常业务而言消息重复是不可避免的,因此我们只能从另一个角度来解决重复消息的问题。关键点就是幂等。既然我们不能防止重复消息的产生,那么我们只能在业务上处理重复消息所带来的影响。
  1. SQL前置条件判断,添加Version版本号控制
  2. 通过数据库约束例如唯一键。
  3. 记录关键的信息,比如处理订单这种,记录订单ID,假如有重复的消息过来,先判断下这个ID是否 已经被处理过了,如果没处理再进行下一步。

7. 如何保证消息的有序性消费?

首先消息有序分为全局有序和部分有序

  1. 全局有序
  • 如果要保证消息的全局有序,首先只能由一个生产者往 Topic 发送消息,并且一个 Topic 内部只能有 一个队列(分区)。消费者也必须是单线程消费这个队列。这样的消息就是全局有序的!
  1. 部分有序
  • 部分有序我们就可以将 Topic 内部划分成我们需要的分区数,把消息通过特定的策略发往固定的分区中,然后每个队列对应一个单线程处理的消费者。这样即完成了部分有序的需求。

8.如何处理消息积压?

消息的堆积往往是因为生产者的生产速度与消费者的消费速度不匹配。

  1. 可能是因为消息消费失败反复重试造成的;

  2. 也有可能就是消费者消费能力弱,渐渐地消息就积压了。

  • 因此我们需要先定位消费慢的原因,如果是 bug 则处理 bug ,如果是因为本身消费能力较弱,我们可以优化下消费逻辑,比如之前是一条一条消息消费处理的,这次我们批量处理,比如数据库的插入,可以将一条一条插入改成批量插入。
  • 假如逻辑我们已经都优化了,但还是慢,那就得考虑水平扩容了,增加 Topic 的分区数和消费者数量。

9. kafka的消费者是pull(拉)还是push(推)模式,这种模式有什么好处?

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

优点:pull模式消费者自主决定是否批量从broker拉取数据,而push模式在无法知道消费者消费能力情况下,不易控制推送速度,太快可能造成消费者奔溃,太慢又可能造成浪费。

10. zookeeper对于kafka的作用是什么?

Zookeeper 主要用于在集群中不同节点之间进行通信,在 Kafka 中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都可以从之前提交的偏移量中获取,除此之外,它还执行其他活动,如: leader 检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。

11. 讲一讲 kafka ack 的三种确认机制?

0:生产者不会等待 broker 的 ack,这个延迟最低但是存储的保证最弱当 server 挂掉的时候就会丢数据。 1:服务端会等待 ack 值 leader 副本确认接收到消息后发送 ack 但是如果 leader挂掉后他不确保是否复制完成新 leader 也会导致数据丢失。 -1(all):服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出的ack,这样数据不会丢失。

12. kafka如何实现数据的高效读取?(顺序读写、分段命令、二分查找)

Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为index。 index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。

13. Kafka 消费者端的 Rebalance 操作什么时候发生?

  1. 同一个 consumer 消费者组 group.id 中,新增了消费者进来,会执行 Rebalance 操作

  2. 消费者离开当期所属的 consumer group组。比如宕机

  3. 分区数量发生变化时(即 topic 的分区数量发生变化时)

  4. 消费者主动取消订阅

  • Rebalance的过程如下:
  1. 所有成员都向coordinator(每个消费者组会选择一个broker作为自己的coordinator)发送请求,请求入组。一旦所有成员都发送了请求,coordinator会从中选择一个consumer担任leader的角色,并把组成员信息以及订阅信息发给leader。
  2. leader开始分配消费方案,指明具体哪个consumer负责消费哪些topic的哪些partition。一旦完成分配,leader会将这个方案发给coordinator。coordinator接收到分配方案之后会把方案发给各个consumer,这样组内的所有成员就都知道自己应该消费哪些分区了。

14. kafka的速度为什么这么快?

主要从这 3 个角度来分析:

  • 生产端
  • 服务端 Broker
  • 消费端

kafka为什么那么快.png

  1. 利用 Partition 实现并行处理

  • 每个 Topic 都包含一个或多个 Partition,不同 Partition 可位于不同节点。

    一方面,由于不同 Partition 可位于不同机器,因此可以充分利用集群优势,实现机器间的并行处理。另一方面,由于 Partition 在物理上对应一个文件夹,即使多个 Partition 位于同一个节点,也可通过配置让同一节点上的不同 Partition 置于不同的磁盘上,从而实现磁盘间的并行处理,充分发挥多磁盘的优势。

  1. 顺序写磁盘,充分利用磁盘特性
  • Kafka 中每个分区是一个有序的,不可变的消息序列,新的消息不断追加到 partition 的末尾,这个就是顺序写。

  1. 充分利用 Page Cache(页缓存)

  2. 零拷贝技术

  3. 批处理

  • Kafka 的客户端和 broker 还会在通过网络发送数据之前,在一个批处理中累积多条记录 (包括读和写)。记录的批处理分摊了网络往返的开销,使用了更大的数据包从而提高了带宽利用率。

  1. 数据压缩
  • Producer 可将数据压缩后发送给 broker,从而减少网络传输代价,目前支持的压缩算法有:Snappy、Gzip、LZ4。数据压缩一般都是和批处理配套使用来作为优化手段的。

15 .说说kafka的零拷贝机制?

所谓的零拷贝是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之手。零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换。

在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上,那么它必须要经过几个拷贝的过程:

  1. 从磁盘中读取目标文件内容拷贝到内核缓冲区
  2. 再把内核缓冲区的数据赋值到用户空间的缓冲区中
  3. 接着在应用程序中,调用write()方法,把用户空间缓冲区中的数据拷贝到内核下的Socket Buffer中。
  4. 最后,把在内核模式下的SocketBuffer中的数据赋值到网卡缓冲区(NIC Buffer)
  5. 网卡缓冲区再把数据传输到目标服务器上。

在这个过程中我们可以发现,数据从磁盘到最终发送出去,要经历4次拷贝,而在这四次拷贝过程中,有两次拷贝是浪费的,分别是:

  1. 从内核空间赋值到用户空间
  2. 从用户空间再次复制到内核空间

除此之外,由于用户空间和内核空间的切换会带来CPU的上线文切换,对于CPU性能也会造成性能影响。

而零拷贝,就是把这两次多于的拷贝省略掉,应用程序可以直接把磁盘中的数据从内核中直接传输给Socket,而不需要再经过应用程序所在的用户空间。

零拷贝通过DMA(Direct Memory Access)技术把文件内容复制到内核空间中的Read Buffer,接着把包含数据位置和长度信息的文件描述符加载到Socket Buffer中,DMA引擎直接可以把数据从内核空间中传递给网卡设备。

在这个流程中,数据只经历了两次拷贝就发送到了网卡中,并且减少了2次cpu的上下文切换,对于效率有非常大的提高。

所以,所谓零拷贝,并不是完全没有数据赋值,只是相对于用户空间来说,不再需要进行数据拷贝。对于前面说的整个流程来说,零拷贝只是减少了不必要的拷贝次数而已。

16. kafka分区Leader选举策略有几种?

分区的Leader副本选举对用户是完全透明的,它是由Controller独立完成的。你需要回答的是,在哪些场景下,需要执行分区Leader选举。每一种场景对应于一种选举策略。

  1. OfflinePartition Leader选举:每当有分区上线时,就需要执行Leader选举。所谓的分区上线,可能是创建了新分区,也可能是之前的下线分区重新上线。这是最常见的分区Leader选举场景。

  2. ReassignPartition Leader选举:当你手动运行Kafka-reassign-partitions命令,或者是调用Admin的alterPartitionReassignments方法执行分区副本重分配时,可能触发此类选举。假设原来的AR是[1,2,3],Leader是1,当执行副本重分配后,副本集合AR被设置成[4,5,6],显然,Leader必须要变更,此时会发生Reassign Partition Leader选举。

  3. PreferredReplicaPartition Leader选举:当你手动运行Kafka-preferred-replica-election命令,或自动触发了Preferred Leader选举时,该类策略被激活。所谓的Preferred Leader,指的是AR中的第一个副本。比如AR是[3,2,1],那么,Preferred Leader就是3。

  4. ControlledShutdownPartition Leader选举:当Broker正常关闭时,该Broker上的所有Leader副本都会下线,因此,需要为受影响的分区执行相应的Leader选举。

这4类选举策略的大致思想是类似的,即从AR中挑选首个在ISR中的副本,作为新Leader。

17. Kafka 是如何实现exactly once语义的?

在producer推送数据至topic的过程中也可能会遇到网络问题。根据producer处理此类故障所采取的提交策略类型,我们可以获得三种语义:

  1. 至少一次(at-least-once):消息不会丢失,但有可能被重复发送。

  2. 最多一次(at-most-once):消息可能会丢失,但绝不会被重复发送。

  3. 精确一次(exactly-once):消息不会丢失,也不会被重复发送。

  • Kafka 默认提供的交付可靠性保障是第一种,即至少一次

  • Kafka 也可以提供最多一次交付保障,只需要让 Producer 禁止重试即可。

  • Kafka 分别通过 幂等性(Idempotence)和事务(Transaction)这两种机制实现了 精确一次(exactly once)语义。