无论是作为面试官,还是应聘者,我都接触过很多 Kafka 面试题。而在最近面试了很多候选人,发现写了熟悉Kafka,但是对于Kafka相关的知识却是只知道大概用处,简单搭建和使用。我想说,虽然我们是SRE(可靠性工程师),但不论你是业务层的SRE还是基础设施层的SRE,我们都需要对业务方的使用场景有足够理解,或者对我们要提供的服务有足够的了解才行,这样你才能整体的保证你的业务连续性以及业务可靠性。因此,专门总结了如下经典的kafka面试详解。

以下面试题,参考胡大的Kafka 核心源码解读,对相关的知识进行了补充和思考。

基础题目

1.Apache Kafka 是什么?

能问这道题,主要是想看候选人对于kafka的使用场景以及定位认知理解有多深,同时候可以知道候选人对于这项技术的关注度。

我们都知道,在开源软件中,大部分软件随着用户量的增加,整个软件的功能和定位也有了新的变化,而Apache Kafka 一路发展到现在,已经由最初的分布式提交日志系统逐渐演变成了实时流处理框架。

因此,这道题你最好这么回答:Apach Kafka 是一款分布式流处理平台,用于实时构建流处理应用。它有一个核心的功能广为人知,即作为企业级的消息引擎被广泛使用(通常也会称之为消息总线message bus)。

关于分布式流处理平台,其实从它官方的logo以及slogan我们就很容易看出来。

kafka-logo

2.什么是消费者组?

消费者组是 Kafka 独有的概念,如果面试官问这个,就说明他对此是有一定了解的。

胡大给的标准答案是: 官网上的介绍言简意赅,即消费者组是 Kafka 提供的可扩展且具有容错性的消费者机制

但实际上,消费者组(Consumer Group)其实包含两个概念,作为队列,消费者组允许你分割数据处理到一组进程集合上(即一个消费者组中可以包含多个消费者进程,他们共同消费该topic的数据),这有助于你的消费能力的动态调整;作为发布-订阅模型(publish-subscribe),kafka允许你将同一份消息广播到多个消费者组里,以此来丰富多种数据使用场景。

需要注意的是: 在消费者组中,多个实例共同订阅若干个主题,实现共同消费。同一个组下的每个实例都配置有相同的组 ID,被分配不同的订阅分区。当某个实例挂掉的时候,其他实例会自动地承担起它负责消费的分区。 因此,消费者组在一定程度上也保证了消费者程序的高可用性。

kafka-consumer-group

注意: 消费者组的题目,能够帮你在某种程度上掌控下面的面试方向。

  • 如果你擅长位移值原理(Offset),就不妨再提一下消费者组的位移提交机制;
  • 如果你擅长Kafka Broker,可以提一下消费者组与 Broker 之间的交互;
  • 如果你擅长与消费者组完全不相关的 Producer,那么就可以这么说:“消费者组要消费的数据完全来自于 Producer 端生产的消息,我对 Producer 还是比较熟悉的。”

总之,你总得对consumer group相关的方向有一定理解,然后才能像面试官表名你对某一块很理解。

3.在 Kafka 中,ZooKeeper 的作用是什么?

这道题,也是我经常会问候选人的题,因为任何分布式系统中虽然都通过一些列的算法去除了传统的关系型数据存储,但是毕竟还是有些数据要存储的,同时分布式系统的特性往往是需要有一些中间人角色来统筹集群。比如我们在整个微服务框架中的Dubbo,它也是需要依赖一些注册中心或配置中心类的中间件的,以及云原生的Kubernetes使用Etcd作为整个集群的枢纽。

标准答案: 目前,Kafka 使用 ZooKeeper 存放集群元数据、成员管理、Controller 选举,以及其他一些管理类任务。之后,等 KIP-500 提案完成后,Kafka 将完全不再依赖于 ZooKeeper。

  • “存放元数据” 是指主题分区的所有数据都保存在 ZooKeeper 中,且以它保存的数据为权威,其他 “人” 都要与它保持对齐。
  • “成员管理” 是指 Broker 节点的注册、注销以及属性变更,等等。
  • “Controller 选举” 是指选举集群 Controller,而其他管理类任务包括但不限于主题删除、参数配置等

KIP-500 思想,是使用社区自研的基于 Raft 的共识算法,替代 ZooKeeper,实现 Controller 自选举。

4.解释下 Kafka 中位移(offset)的作用

标准答案: 在 Kafka 中,每个主题分区下的每条消息都被赋予了一个唯一的 ID 数值,用于标识它在分区中的位置。这个 ID 数值,就被称为位移,或者叫偏移量。一旦消息被写入到分区日志,它的位移值将不能被修改。

答完这些之后,你还可以把整个面试方向转移到你希望的地方:

  • 如果你深谙 Broker 底层日志写入的逻辑,可以强调下消息在日志中的存放格式
  • 如果你明白位移值一旦被确定不能修改,可以强调下 “Log Cleaner 组件都不能影响位移值” 这件事情
  • 如果你对消费者的概念还算熟悉,可以再详细说说位移值消费者位移值之间的区别

5.阐述下 Kafka 中的领导者副本(Leader Replica)和追随者副本(Follower Replica)的区别

推荐的答案: Kafka 副本当前分为领导者副本和追随者副本。只有 Leader 副本才能对外提供读写服务,响应 Clients 端的请求。Follower 副本只是采用拉(PULL)的方式,被动地同步 Leader 副本中的数据,并且在 Leader 副本所在的 Broker 宕机后,随时准备应聘 Leader 副本。

加分点:

  • 强调 Follower 副本也能对外提供读服务。自 Kafka 2.4 版本开始,社区通过引入新的 Broker 端参数,允许 Follower 副本有限度地提供读服务。
  • 强调 Leader 和 Follower 的消息序列在实际场景中不一致。通常情况下,很多因素可能造成leader和follower之间的不同步,比如程序问题,网络问题,broker问题等,短暂的不同步我们可以关注(秒级别),但长时间的不同步可能就需要深入排查了,因为一旦leader所在节点异常,可能直接影响可用性。

注意: 之前确保一致性的主要手段是高水位机制(HW),但高水位值无法保证 Leader 连续变更场景下的数据一致性,因此,社区引入了Leader Epoch 机制,来修复高水位值的弊端。

关于高水位和 Leader Epoch 的讨论

实操题目

6.如何设置 Kafka 能接收的最大消息的大小?

对于SRE来讲,该题简直是送分题啊,但是,最大消息的设置通常情况下有生产者端,消费者端,broker端和topic级别的参数,我们需要正确设置,以保证可以正常的生产和消费。

  • Broker端参数: message.max.bytes,max.message.bytes(topic级别),replica.fetch.max.bytes(否则follow会同步失败)
  • Consumer端参数: fetch.message.max.bytes

7.监控 Kafka 的框架都有哪些?

对于SRE来讲,依然是送分题。但基础的我们要知道,kafka本身是提供了jmx(Java Management Extensions)的,我们可以通过它来获取到kafka内部的一些基本数据。

  • Kafka Manager: 更多是kafka的管理,对于SRE非常友好,也提供了简单的瞬时指标监控
  • Kafka Monitor: LinkedIn 开源的免费框架,支持对集群进行系统测试,并实时监控测试结果。
  • CruiseControl: 也是 LinkedIn 公司开源的监控框架,用于实时监测资源使用率,以及提供常用运维操作等。无 UI 界面,只提供 REST API,可以进行多集群管理。
  • JMX 监控: 由于 Kafka 提供的监控指标都是基于 JMX 的,因此,市面上任何能够集成 JMX 的框架都可以使用,比如 Zabbix 和 Prometheus。
  • 已有大数据平台自己的监控体系: 像 Cloudera 提供的 CDH 这类大数据平台,天然就提供 Kafka 监控方案。
  • JMXTool: 社区提供的命令行工具,能够实时监控 JMX 指标。可以使用kafka-run-class.sh kafka.tools.JmxTool来查看具体的用法。

8.Broker 的 Heap Size 如何设置?

其实对于SRE还是送分题,因为目前来讲大部分公司的业务系统都是使用Java开发,因此SRE对于基本的JVM相关的参数应该至少都是非常了解的,核心就在于JVM的配置以及GC相关的知识。

标准答案: 任何 Java 进程 JVM 堆大小的设置都需要仔细地进行考量和测试。一个常见的做法是,以默认的初始 JVM 堆大小运行程序,当系统达到稳定状态后,手动触发一次 Full GC,然后通过 JVM 工具查看 GC 后的存活对象大小。之后,将堆大小设置成存活对象总大小的 1.5~2 倍。对于 Kafka 而言,这个方法也是适用的。不过,业界有个最佳实践,那就是将 Broker 的 Heap Size 固定为 6GB。经过很多公司的验证,这个大小是足够且良好的。

9.如何估算 Kafka 集群的机器数量?

该题也算是SRE的送分题吧,对于SRE来讲,任何生产的系统第一步需要做的就是容量预估以及集群的架构规划,实际上也就是机器数量和所用资源之间的关联关系,资源通常来讲就是cpu,内存,磁盘容量,带宽。但需要注意的是,kafka因为独有的设计,对于磁盘的要求并不是特别高,普通机械硬盘足够,而通常的瓶颈会出现在带宽上。

在预估磁盘的占用时,你一定不要忘记计算副本同步的开销。如果一条消息占用 1KB 的磁盘空间,那么,在有 3 个副本的主题中,你就需要 3KB 的总空间来保存这条消息。同时,需要考虑到整个业务Topic数据保存的最大时间,以上几个因素,基本可以预估出来磁盘的容量需求。

需要注意的是: 对于磁盘来讲,一定要提前和业务沟通好场景,而不是等待真正有磁盘容量瓶颈了才去扩容磁盘或者找业务方沟通方案。

对于带宽来说,常见的带宽有 1Gbps 和 10Gbps,通常我们需要知道,当带宽占用接近总带宽的 90% 时,丢包情形就会发生。

10.Leader 总是 -1,怎么破?

对于有经验的SRE来讲,早期的kafka版本应该多多少少都遇到过该种情况,通常情况下就是controller不工作了,导致无法分配leader,那既然知道问题后,解决方案也就很简单了。重启controller节点上的kafka进程,让其他节点重新注册controller角色,但是如上面zookeeper的作用,你要知道为什么controller可以自动注册。

当然了,当你知道controller的注册机制后,你也可以说: 删除 ZooKeeper 节点 /controller,触发 Controller 重选举。Controller 重选举能够为所有主题分区重刷分区状态,可以有效解决因不一致导致的 Leader 不可用问题。但是,需要注意的是,直接操作zookeeper是一件风险很大的操作,就好比在Linux中执行了rm -rf /xxx一样,如果在/xxx之间不小心多了几个空格,那"恭喜你",今年白干了。

炫技式题目

11.LEO、LSO、AR、ISR、HW 都表示什么含义?

讲真,我不认为这是炫技的题目,特别是作为SRE来讲,对于一个开源软件的原理以及概念的理解,是非常重要的。

  • LEO(Log End Offset): 日志末端位移值或末端偏移量,表示日志下一条待插入消息的位移值。举个例子,如果日志有 10 条消息,位移值从 0 开始,那么,第 10 条消息的位移值就是 9。此时,LEO = 10。
  • LSO(Log Stable Offset): 这是 Kafka 事务的概念。如果你没有使用到事务,那么这个值不存在(其实也不是不存在,只是设置成一个无意义的值)。该值控制了事务型消费者能够看到的消息范围。它经常与 Log Start Offset,即日志起始位移值相混淆,因为有些人将后者缩写成 LSO,这是不对的。在 Kafka 中,LSO 就是指代 Log Stable Offset。
  • AR(Assigned Replicas): AR 是主题被创建后,分区创建时被分配的副本集合,副本个数由副本因子决定。
  • ISR(In-Sync Replicas): Kafka 中特别重要的概念,指代的是 AR 中那些与 Leader 保持同步的副本集合。在 AR 中的副本可能不在 ISR 中,但 Leader 副本天然就包含在 ISR 中。
  • HW(High watermark): 高水位值,这是控制消费者可读取消息范围的重要字段。一个普通消费者只能 “看到” Leader 副本上介于 Log Start Offset 和 HW(不含)之间的所有消息。水位以上的消息是对消费者不可见的。

需要注意的是,通常在ISR中,可能会有人问到为什么有时候副本不在ISR中,这其实也就是上面说的Leader和Follower不同步的情况,为什么我们前面说,短暂的不同步我们可以关注,但是长时间的不同步,我们需要介入排查了,因为ISR里的副本后面都是通过replica.lag.time.max.ms,即Follower 副本的 LEO 落后 Leader LEO 的时间是否超过阈值来决定副本是否在ISR内部的。

12.Kafka 能手动删除消息吗?

Kafka 不需要用户手动删除消息。它本身提供了留存策略,能够自动删除过期消息。当然,它是支持手动删除消息的。

  • 对于设置了 Key 且参数 cleanup.policy=compact 的主题而言,我们可以构造一条 的消息发送给 Broker,依靠 Log Cleaner 组件提供的功能删除掉该 Key 的消息。
  • 对于普通主题而言,我们可以使用 kafka-delete-records 命令,或编写程序调用 Admin.deleteRecords 方法来删除消息。这两种方法殊途同归,底层都是调用 Admin 的 deleteRecords 方法,通过将分区 Log Start Offset 值抬高的方式间接删除消息。

13.__consumer_offsets 是做什么用的?

这是一个内部主题,主要用于存储消费者的偏移量,以及消费者的元数据信息(消费者实例,消费者id等等)

需要注意的是: Kafka 的 GroupCoordinator 组件提供对该主题完整的管理功能,包括该主题的创建、写入、读取和 Leader 维护等。

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

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

  • OfflinePartition Leader 选举: 每当有分区上线时,就需要执行 Leader 选举。所谓的分区上线,可能是创建了新分区,也可能是之前的下线分区重新上线。这是最常见的分区 Leader 选举场景。
  • ReassignPartition Leader 选举: 当你手动运行 kafka-reassign-partitions 命令,或者是调用 Admin 的 alterPartitionReassignments 方法执行分区副本重分配时,可能触发此类选举。假设原来的 AR 是 [1,2,3],Leader 是 1,当执行副本重分配后,副本集合 AR 被设置成 [4,5,6],显然,Leader 必须要变更,此时会发生 Reassign Partition Leader 选举。
  • PreferredReplicaPartition Leader 选举: 当你手动运行 kafka-preferred-replica-election 命令,或自动触发了 Preferred Leader 选举时,该类策略被激活。所谓的 Preferred Leader,指的是 AR 中的第一个副本。比如 AR 是 [3,2,1],那么,Preferred Leader 就是 3。
  • ControlledShutdownPartition Leader 选举: 当 Broker 正常关闭时,该 Broker 上的所有 Leader 副本都会下线,因此,需要为受影响的分区执行相应的 Leader 选举。

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

15.Kafka 的哪些场景中使用了零拷贝(Zero Copy)

其实这道题对于SRE来讲,有点超纲了,不过既然Zero Copy是kafka高性能的保证,我们需要了解它。

Zero Copy 是特别容易被问到的高阶题目。在 Kafka 中,体现 Zero Copy 使用场景的地方有两处:基于 mmap 的索引和日志文件读写所用的 TransportLayer

先说第一个。索引都是基于 MappedByteBuffer 的,也就是让用户态和内核态共享内核态的数据缓冲区,此时,数据不需要复制到用户态空间。不过,mmap 虽然避免了不必要的拷贝,但不一定就能保证很高的性能。在不同的操作系统下,mmap 的创建和销毁成本可能是不一样的。很高的创建和销毁开销会抵消 Zero Copy 带来的性能优势。由于这种不确定性,在 Kafka 中,只有索引应用了 mmap,最核心的日志并未使用 mmap 机制。

再说第二个。TransportLayer 是 Kafka 传输层的接口。它的某个实现类使用了 FileChannel 的 transferTo 方法。该方法底层使用 sendfile 实现了 Zero Copy。对 Kafka 而言,如果 I/O 通道使用普通的 PLAINTEXT,那么,Kafka 就可以利用 Zero Copy 特性,直接将页缓存中的数据发送到网卡的 Buffer 中,避免中间的多次拷贝。相反,如果 I/O 通道启用了 SSL,那么,Kafka 便无法利用 Zero Copy 特性了。

深度思考题

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

这其实是分布式场景下的通用问题,因为我们知道CAP理论下,我们只能保证C(可用性)和A(一致性)取其一,如果支持读写分离,那其实对于一致性的要求可能就会有一定折扣,因为通常的场景下,副本之间都是通过同步来实现副本数据一致的,那同步过程中肯定会有时间的消耗,如果支持了读写分离,就意味着可能的数据不一致,或数据滞后。

Leader/Follower 模型并没有规定 Follower 副本不可以对外提供读服务。很多框架都是允许这么做的,只是 Kafka 最初为了避免不一致性的问题,而采用了让 Leader 统一提供服务的方式。

不过,自 Kafka 2.4 之后,Kafka 提供了有限度的读写分离,也就是说,Follower 副本能够对外提供读服务

17.如何调优 Kafka?

作为SRE来讲,任何生产环境的调优,首先需要识别问题和瓶颈点,而不是随意的进行臆想调优。随后,需要确定优化目标,并且定量给出目标

对于kafka来讲,常见的调优方向基本为: 吞吐量、延时、持久性和可用性,每种目标之前都是由冲突点,这也就要求了,我们在对业务接入使用时,要进行业务场景的了解,以对业务进行相对的集群隔离,因为每一个方向的优化思路都是不同的,甚至是相反的。

确定了目标之后,还要明确优化的维度。有些调优属于通用的优化思路,比如对操作系统、JVM 等的优化;有些则是有针对性的,比如要优化 Kafka 的 TPS。我们需要从 3 个方向去考虑:

  • Producer 端: 增加batch.sizelinger.ms,启用压缩,关闭重试
  • Broker 端: 增加num.replica.fetchers提升 Follower 同步 TPS,避免 Broker Full GC 等。
  • Consumer: 增加fetch.min.bytes

18.Controller 发生网络分区(Network Partitioning)时,Kafka 会怎么样

这道题目能够诱发我们对分布式系统设计、CAP 理论、一致性等多方面的思考。

一旦发生 Controller 网络分区,那么,第一要务就是查看集群是否出现 “脑裂”,即同时出现两个甚至是多个 Controller 组件。这可以根据 Broker 端监控指标 ActiveControllerCount 来判断。

不过,通常而言,我们在设计整个部署架构时,为了避免这种网络分区的发生,一般会将broker节点尽可能的防止在一个机房或者可用区。

由于 Controller 会给 Broker 发送 3 类请求,LeaderAndIsrRequestStopReplicaRequestUpdateMetadataRequest,因此,一旦出现网络分区,这些请求将不能顺利到达 Broker 端。

这将影响主题的创建、修改、删除操作的信息同步,表现为集群仿佛僵住了一样,无法感知到后面的所有操作。因此,网络分区通常都是非常严重的问题,要赶快修复。

19.Java Consumer 为什么采用单线程来获取消息?

在回答之前,如果先把这句话说出来,一定会加分:Java Consumer 是双线程的设计。一个线程是用户主线程,负责获取消息;另一个线程是心跳线程,负责向 Kafka 汇报消费者存活情况。将心跳单独放入专属的线程,能够有效地规避因消息处理速度慢而被视为下线的 “假死” 情况。

单线程获取消息的设计能够避免阻塞式的消息获取方式。单线程轮询方式容易实现异步非阻塞式,这样便于将消费者扩展成支持实时流处理的操作算子。因为很多实时流处理操作算子都不能是阻塞式的。另外一个可能的好处是,可以简化代码的开发。多线程交互的代码是非常容易出错的。

20.简述 Follower 副本消息同步的完整流程

首先,Follower 发送 FETCH 请求给 Leader。

接着,Leader 会读取底层日志文件中的消息数据,再更新它内存中的 Follower 副本的 LEO 值,更新为 FETCH 请求中的 fetchOffset 值。

最后,尝试更新分区高水位值。Follower 接收到 FETCH 响应之后,会把消息写入到底层日志,接着更新 LEO 和 HW 值。

Leader 和 Follower 的 HW 值更新时机是不同的,Follower 的 HW 更新永远落后于 Leader 的 HW。这种时间上的错配是造成各种不一致的原因。

因此,对于消费者而言,消费到的消息永远是所有副本中最小的那个HW。

胡大的kafka源码课程


公众号