Java工程师面试题-中间件-Kafka

推荐先阅读Java工程师面试题

“你用过消息中间件吗?用过哪些?”

这是在面试过程中面试官必问的一个问题,但是我真的听过很多人说没用过,也有人说用过但也仅仅知道怎么调用,其他的问题一概不知,在消息中间件在项目中发挥着中流砥柱作用的今天,仅仅知道调用显然是不够的的了,为了能让乡亲们多点底气,今天为大家带来Kafka的高频面试题(kafka我用的比较多)。

1、为什么要使用 kafka,为什么要使用消息队列?

  • 缓冲和削峰:上游数据时有突发流量,下游可能扛不住,或者下游没有足够多的机器来保证冗余,kafka在中间可以起到一个缓冲的作用,把消息暂存在kafka中,下游服务就可以按照自己的节奏进行慢慢处理。
  • 解耦和扩展性:项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。
  • 冗余:可以采用一对多的方式,一个生产者发布消息,可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。
  • 健壮性:消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。
  • 异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

2、Kafka的常用组件有哪些?

  • producer:消息的生产者, 自己决定哪个 partions 中生产消息, 两种机制:hash 与 轮询。
  • consumer:通过 zookeeper 进行维护消费者偏移量, consumer有自己的消费组,不同组之间维护同一个 topic 数据,互不影响.相同组的不同 consumer消费同一个 topic,这个 topic相同的数据只被消费一次。
  • broker:broker 组成 kafka 集群的节点,之间没有主从关系, 依赖 zookeeper进行协调, broker 负责消息的读写与存储, 一个 broker可以管理读个
  • partionstopic:一类消息的总称/消息队里, topic是由 partions组成, 一个 topic 由多台 server 里的 partions 组成。zookeeper 协调 kafka broker,存储元数据, consumer的 offset+ broker 信息 +topic信息+ partions信息partions 组成 topic 的单元, 每个 topic有副本(创建 topic 指定), 每个 partions 只能有有个 broker管理

3、数据传输的事物定义有哪三种?

数据传输的事务定义通常有以下三种级别:

  • 最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输。
  • 最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输。
  • 精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而 且仅仅被传输一次,这是大家所期望的。

4、ZooKeeper在Kafka中的作用是什么?

Apache Kafka是一个使用Zookeeper构建的分布式系统。虽然,Zookeeper的主要作用是在集群中的不同节点之间建立协调。但是,如果任何节点失败,我们还使用Zookeeper从先前提交的偏移量中恢复,因为它做周期性提交偏移量工作。

5、没有ZooKeeper可以使用Kafka吗?

zookeeper 是一个分布式的协调组件,早期版本的kafka用zk做meta信息存储,consumer的消费状态,group的管理以及 offset的值。考虑到zk本身的一些因素以及整个架构较大概率存在单点问题,新版本中逐渐弱化了zookeeper的作用。新的consumer使用了kafka内部的group coordination协议,也减少了对zookeeper的依赖。但是broker依然依赖于ZK,zookeeper 在kafka中还用来选举controller 和 检测broker是否存活等等。

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

  • 节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接。
  • 如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久。

7、解释偏移的作用。

给分区中的消息提供了一个顺序ID号,我们称之为偏移量。因此,为了唯一地识别分区中的每条消息,我们使用这些偏移量。

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

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

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

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

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

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

  • 消息长度: 4 bytes (value: 1+4+n)
  • 版本号: 1 byte
  • CRC 校验码: 4 bytes
  • 具体的消息: n bytes

kafka follower如何与leader同步数据?

Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制。完全同步复制要求All Alive Follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率。而异步复制方式下,Follower异步的从Leader复制数据,数据只要被Leader写入log就被认为已经commit,这种情况下,如果leader挂掉,会丢失数据,kafka使用ISR的方式很好的均衡了确保数据不丢失以及吞吐率。Follower可以批量的从Leader复制数据,而且Leader充分利用磁盘顺序读以及send file(zero copy)机制,这样极大的提高复制性能,内部批量写磁盘,大幅减少了Follower与Leader的消息量差。

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

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

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

  3. 通过 index 元数据全部映射到 memory,可以避免 segment file 的 IO 磁盘操作。

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

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

  • Kafka 持久化日志,这些日志可以被重复读取和无限期保留
  • Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过复制数据 提升容错能力和高可用性
  • Kafka 支持实时的流式处理

Kafka为什么那么快?**

  • Cache Filesystem Cache PageCache缓存
  • 顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。
  • Zero-copy 零拷技术减少拷贝次数
  • Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。
  • Pull 拉模式 使用拉模式进行消息的获取消费,与消费端处理能力相符。

什么情况下一个 broker 会从 isr中踢出去?**

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

kafka producer如何优化打入速度?**

  • 增加线程
  • 提高 batch.size
  • 增加更多 producer 实例
  • 增加 partition 数
  • 设置 acks=-1 时,如果延迟增大:可以增大 num.replica.fetchers(follower 同步数据的线程数)来调解;
  • 跨数据中心的传输:增加 socket 缓冲区设置以及 OS tcp 缓冲区设置。

kafka producer 打数据,ack 为 0, 1, -1 的时候代表啥(ack机制), 设置 -1 的时候,什么情况下,leader 会认为一条消息 commit了?**

  • 1(默认) 数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了。在这种情况下,如果leader宕机了,则会丢失数据。
  • 0 生产者将数据发送出去就不管了,不去等待任何返回。这种情况下数据传输效率最高,但是数据可靠性确是最低的。
  • -1 producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。当ISR中所有Replica都向Leader发送ACK时,leader才commit,这时候producer才能认为一个请求中的消息都commit了。

Kafka中的消息是否会丢失和重复消费?**

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

1、消息发送

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

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

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

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

2、消息消费

Kafka消息消费有两个consumer接口,Low-level API和High-level API

  • Low-level API:消费者自己维护offset等值,可以实现对Kafka的完全控制;
  • High-level API:封装了对parition和offset的管理,使用简单;

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

解决办法

  • 针对消息丢失:同步模式下,确认机制设置为-1,即让消息写入Leader和Follower之后再确认消息发送成功;异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态;

  • 针对消息重复:将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可。

    消息重复消费及解决参考:https://www.javazhiyin.com/22910.html

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

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

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

  1. 数据一致性问题。数据从主节点转到从节点必然会有一个延时的时间窗口,这个时间 窗口会导致主从节点之间的数据不一致。某一时刻,在主节点和从节点中 A 数据的值都为 X, 之后将主节点中 A 的值修改为 Y,那么在这个变更通知到从节点之前,应用读取从节点中的 A 数据的值并不为最新的 Y,由此便产生了数据不一致的问题。
  2. 延时问题。类似 Redis 这种组件,数据从写入主节点到同步至从节点中的过程需要经 历网络→主节点内存→网络→从节点内存这几个阶段,整个过程会耗费一定的时间。而在 Kafka 中,主从同步会比 Redis 更加耗时,它需要经历网络→主节点内存→主节点磁盘→网络→从节 点内存→从节点磁盘这几个阶段。对延时敏感的应用而言,主写从读的功能并不太适用。

Kafka中是怎么体现消息顺序性的?**

kafka每个partition中的消息在写入时都是有序的,消费时,每个partition只能被每一个group中的一个消费者消费,保证了消费时也是有序的。

整个topic不保证有序。如果为了保证topic整个有序,那么将partition调整为1.

消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?**

offset+1

kafka如何实现延迟队列?**

Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定时器(SystemTimer)。JDK的Timer和DelayQueue插入和删除操作的平均时间复杂度为O(nlog(n)),并不能满足Kafka的高性能要求,而基于时间轮可以将插入和删除操作的时间复杂度都降为O(1)。时间轮的应用并非Kafka独有,其应用场景还有很多,在Netty、Akka、Quartz、Zookeeper等组件中都存在时间轮的踪影。

底层使用数组实现,数组中的每个元素可以存放一个TimerTaskList对象。TimerTaskList是一个环形双向链表,在其中的链表项TimerTaskEntry中封装了真正的定时任务TimerTask.

Kafka中到底是怎么推进时间的呢?Kafka中的定时器借助了JDK中的DelayQueue来协助推进时间轮。具体做法是对于每个使用到的TimerTaskList都会加入到DelayQueue中。Kafka中的TimingWheel专门用来执行插入和删除TimerTaskEntry的操作,而DelayQueue专门负责时间推进的任务。再试想一下,DelayQueue中的第一个超时任务列表的expiration为200ms,第二个超时任务为840ms,这里获取DelayQueue的队头只需要O(1)的时间复杂度。如果采用每秒定时推进,那么获取到第一个超时的任务列表时执行的200次推进中有199次属于“空推进”,而获取到第二个超时任务时有需要执行639次“空推进”,这样会无故空耗机器的性能资源,这里采用DelayQueue来辅助以少量空间换时间,从而做到了“精准推进”。Kafka中的定时器真可谓是“知人善用”,用TimingWheel做最擅长的任务添加和删除操作,而用DelayQueue做最擅长的时间推进工作,相辅相成。

参考文章:https://blog.csdn.net/u013256816/article/details/80697456

参考文章:https://blog.csdn.net/qq_28900249/article/details/90346599

参考文章:https://www.cnblogs.com/kx33389/p/11182082.html

kafka怎么处理消息顺序、重复发送、重复消费、消息丢失

Kafka在什么情况下会出现消息丢失及解决方案?

  1. 消息发送

    1. ack=0,不重试
      producer发送消息完,不管结果了,如果发送失败也就丢失了。
    2. ack=1,leader crash
      producer发送消息完,只等待lead写入成功就返回了,leader crash了,这时follower没来及同步,消 息丢失。
    3. unclean.leader.election.enable 配置true
      允许选举ISR以外的副本作为leader,会导致数据丢失,默认为false。producer发送异步消息完,只等待 lead写入成功就返回了,leader crash了,这时ISR中没有follower,leader从OSR中选举,因为OSR 中本来落后于Leader造成消息丢失。

    解决方案:

    1. 配置:ack=all / -1,tries > 1,unclean.leader.election.enable : false producer发送消息完,等待follower同步完再返回,如果异常则重试。副本的数量可能影响吞吐量。

      不允许选举ISR以外的副本作为leader。

    2. 配置:min.insync.replicas > 1

      副本指定必须确认写操作成功的最小副本数量。如果不能满足这个最小值,则生产者将引发一个异常(要么是 NotEnoughReplicas,要么是NotEnoughReplicasAfterAppend)。
      min.insync.replicas和ack更大的持久性保证。确保如果大多数副本没有收到写操作,则生产者将引发异 常。

    3. 失败的offset单独记录
      producer发送消息,会自动重试,遇到不可恢复异常会抛出,这时可以捕获异常记录到数据库或缓存,进行 单独处理。

  2. 消费

    先commit再处理消息。如果在处理消息的时候异常了,但是offset 已经提交了,这条消息对于该消费者来 说就是丢失了,再也不会消费到了。

  3. broker的刷盘

    减小刷盘间隔

Kafka是pull?push?优劣势分析

pull模式:

  • 根据consumer的消费能力进行数据拉取,可以控制速率
  • 可以批量拉取、也可以单条拉取
  • 可以设置不同的提交方式,实现不同的传输语义

缺点:如果kafka没有数据,会导致consumer空循环,消耗资源

解决:通过参数设置,consumer拉取数据为空或者没有达到一定数量时进行阻塞

push模式:不会导致consumer循环等待

优点:不会导致consumer循环等待

缺点:速率固定、忽略了consumer的消费能力,可能导致拒绝服务或者网络拥塞等情况

Kafka中zk的作用

/brokers/ids:临时节点,保存所有broker节点信息,存储broker的物理地址、版本信息、启动时间 等,节点名称为brokerID,broker定时发送心跳到zk,如果断开则该brokerID会被删除

/brokers/topics:临时节点,节点保存broker节点下所有的topic信息,每一个topic节点下包含一个固 定的partitions节点,partitions的子节点就是topic的分区,每个分区下保存一个state节点、保存着当 前leader分区和ISR的brokerID,state节点由leader创建,若leader宕机该节点会被删除,直到有新的 leader选举产生、重新生成state节点

/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]:维护消费者和分区的注册关系

/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]:分区消息的消费进度Offset

client通过topic找到topic树下的state节点、获取leader的brokerID,到broker树中找到broker的物理 地址,但是client不会直连zk,而是通过配置的broker获取到zk中的信息

简述kafka的rebalance机制

consumer group中的消费者与topic下的partion重新匹配的过程

何时会产生rebalance:

  • consumer group中的成员个数发生变化
  • consumer消费超时
  • group订阅的topic个数发生变化
  • group订阅的topic的分区数发生变化

coordinator:通常是partition的leader节点所在的broker,负责监控group中consumer的存活, consumer维持到coordinator的心跳,判断consumer的消费超时

  • coordinator通过心跳返回通知consumer进行rebalance
  • consumer请求coordinator加入组,coordinator选举产生leader consumer
  • leader consumer从coordinator获取所有的consumer,发送syncGroup(分配信息)给到 coordinator
  • coordinator通过心跳机制将syncGroup下发给consumer
  • 完成rebalance

leader consumer监控topic的变化,通知coordinator触发rebalance

如果C1消费消息超时,触发rebalance,重新分配后、该消息会被其他消费者消费,此时C1消费完成提 交offset、导致错误

解决:coordinator每次rebalance,会标记一个Generation给到consumer,每次rebalance该 Generation会+1,consumer提交offset时,coordinator会比对Generation,不一致则拒绝提交

Kafka的性能好在什么地方

kafka不基于内存,而是硬盘存储,因此消息堆积能力更强

顺序写:利用磁盘的顺序访问速度可以接近内存,kafka的消息都是append操作,partition是有序的, 节省了磁盘的寻道时间,同时通过批量操作、节省写入次数,partition物理上分为多个segment存储, 方便删除

传统:

  • 读取磁盘文件数据到内核缓冲区
  • 将内核缓冲区的数据copy到用户缓冲区
  • 将用户缓冲区的数据copy到socket的发送缓冲区
  • 将socket发送缓冲区中的数据发送到网卡、进行传输

零拷贝:

  • 直接将内核缓冲区的数据发送到网卡传输
  • 使用的是操作系统的指令支持

kafka不太依赖jvm,主要理由操作系统的pageCache,如果生产消费速率相当,则直接用pageCache 交换数据,不需要经过磁盘IO