无论是作为面试官,还是应聘者,我都接触过很多Kafka面试题。而在最近面试了很多候选人,发现写了熟悉Kafka,但是对于Kafka相关的知识却是只知道大概用处,简单搭建和使用。我想说,虽然我们是SRE(可靠性工程师),但不论你是业务层的SRE还是基础设施层的SRE,我们都需要对业务方的使用场景有足够理解,或者对我们要提供的服务有足够的了解才行,这样你才能整体的保证你的业务连续性以及业务可靠性。因此,专门总结了如下经典的kafka面试详解。
以下面试题,参考胡夕胡大的Kafka核心源码解读,对相关的知识进行了补充和思考。
基础题目
Apache Kafka是什么?
能问这道题,主要是想看候选人对于Kafka的使用场景以及定位认知理解有多深,同时候可以知道候选人对于这项技术的关注度。
我们都知道,在开源软件中,大部分软件随着用户量的增加,整个软件的功能和定位也有了新的变化,而Apache Kafka一路发展到现在,已经由最初的分布式提交日志系统逐渐演变成了实时流处理框架。
因此,这道题你最好这么回答:Apach Kafka是一款分布式流处理平台,用于实时构建流处理应用。它有一个核心的功能广为人知,即作为企业级的消息引擎被广泛使用(通常也会称之为消息总线message bus)。
关于分布式流处理平台,其实从它官方的Logo以及Slogan我们就很容易看出来。
什么是消费者组?
消费者组是Kafka独有的概念,如果面试官问这个,就说明他对此是有一定了解的。
胡大给的标准答案是:官网上的介绍言简意赅,即消费者组是Kafka提供的可扩展且具有容错性的消费者机制。
但实际上,消费者组(Consumer Group)其实包含两个概念,作为队列,消费者组允许你分割数据处理到一组进程集合上(即一个消费者组中可以包含多个消费者进程,他们共同消费该topic的数据),这有助于你的消费能力的动态调整;作为发布-订阅模型(publish-subscribe),Kafka允许你将同一份消息广播到多个消费者组里,以此来丰富多种数据使用场景。
需要注意的是:在消费者组中,多个实例共同订阅若干个主题,实现共同消费。同一个组下的每个实例都配置有相同的组ID,被分配不同的订阅分区。当某个实例挂掉的时候,其他实例会自动地承担起它负责消费的分区。因此,消费者组在一定程度上也保证了消费者程序的高可用性。
注意:消费者组的题目,能够帮你在某种程度上掌控下面的面试方向。
- 如果你擅长位移值原理(Offset),就不妨再提一下消费者组的位移提交机制;
- 如果你擅长Kafka Broker,可以提一下消费者组与Broker之间的交互;
- 如果你擅长与消费者组完全不相关的Producer,那么就可以这么说:“消费者组要消费的数据完全来自于Producer端生产的消息,我对Producer还是比较熟悉的。”
总之,你总得对consumer group相关的方向有一定理解,然后才能像面试官表名你对某一块很理解。
在Kafka中,ZooKeeper的作用是什么?
这道题,也是我经常会问候选人的题,因为任何分布式系统中虽然都通过一些列的算法去除了传统的关系型数据存储,但是毕竟还是有些数据要存储的,同时分布式系统的特性往往是需要有一些中间人角色来统筹集群。比如我们在整个微服务框架中的Dubbo,它也是需要依赖一些注册中心或配置中心类的中间件的,以及云原生的Kubernetes使用etcd作为整个集群的枢纽。
标准答案:目前,Kafka使用ZooKeeper存放集群元数据、成员管理、Controller选举,以及其他一些管理类任务。之后,等KIP-500提案完成后,Kafka将完全不再依赖于ZooKeeper。
- “存放元数据”是指主题分区的所有数据都保存在 ZooKeeper 中,且以它保存的数据为权威,其他 “人” 都要与它保持对齐。
- “成员管理” 是指 Broker 节点的注册、注销以及属性变更,等等。
- “Controller 选举” 是指选举集群 Controller,而其他管理类任务包括但不限于主题删除、参数配置等。
KIP-500 思想,是使用社区自研的基于Raft的共识算法,替代ZooKeeper,实现Controller自选举。
解释下Kafka中位移(offset)的作用
标准答案:在Kafka中,每个主题分区下的每条消息都被赋予了一个唯一的ID数值,用于标识它在分区中的位置。这个ID数值,就被称为位移,或者叫偏移量。一旦消息被写入到分区日志,它的位移值将不能被修改。
答完这些之后,你还可以把整个面试方向转移到你希望的地方:
- 如果你深谙Broker底层日志写入的逻辑,可以强调下消息在日志中的存放格式
- 如果你明白位移值一旦被确定不能修改,可以强调下“Log Cleaner组件都不能影响位移值”这件事情
- 如果你对消费者的概念还算熟悉,可以再详细说说位移值和消费者位移值之间的区别
阐述下 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机制,来修复高水位值的弊端。
实操题目
如何设置Kafka能接收的最大消息的大小?
对于SRE来讲,该题简直是送分题啊,但是,最大消息的设置通常情况下有生产者端,消费者端,broker端和topic级别的参数,我们需要正确设置,以保证可以正常的生产和消费。
- Broker端参数:message.max.bytes,max.message.bytes(topic级别),replica.fetch.max.bytes(否则follow会同步失败)
- Consumer端参数:fetch.message.max.bytes
监控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来查看具体的用法。
Broker的Heap Size如何设置?
其实对于SRE还是送分题,因为目前来讲大部分公司的业务系统都是使用Java开发,因此SRE对于基本的JVM相关的参数应该至少都是非常了解的,核心就在于JVM的配置以及GC相关的知识。
标准答案:任何Java进程JVM堆大小的设置都需要仔细地进行考量和测试。一个常见的做法是,以默认的初始JVM堆大小运行程序,当系统达到稳定状态后,手动触发一次Full GC,然后通过JVM工具查看GC后的存活对象大小。之后,将堆大小设置成存活对象总大小的1.5~2倍。对于Kafka而言,这个方法也是适用的。不过,业界有个最佳实践,那就是将Broker的Heap Size固定为6GB。经过很多公司的验证,这个大小是足够且良好的。
如何估算Kafka集群的机器数量?
该题也算是SRE的送分题吧,对于SRE来讲,任何生产的系统第一步需要做的就是容量预估以及集群的架构规划,实际上也就是机器数量和所用资源之间的关联关系,资源通常来讲就是CPU,内存,磁盘容量,带宽。但需要注意的是,Kafka因为独有的设计,对于磁盘的要求并不是特别高,普通机械硬盘足够,而通常的瓶颈会出现在带宽上。
在预估磁盘的占用时,你一定不要忘记计算副本同步的开销。如果一条消息占用1KB的磁盘空间,那么,在有3个副本的主题中,你就需要3KB的总空间来保存这条消息。同时,需要考虑到整个业务Topic数据保存的最大时间,以上几个因素,基本可以预估出来磁盘的容量需求。
需要注意的是:对于磁盘来讲,一定要提前和业务沟通好场景,而不是等待真正有磁盘容量瓶颈了才去扩容磁盘或者找业务方沟通方案。
对于带宽来说,常见的带宽有1Gbps和10Gbps,通常我们需要知道,当带宽占用接近总带宽的90%时,丢包情形就会发生。
Leader总是-1,怎么破?
对于有经验的SRE来讲,早期的Kafka版本应该多多少少都遇到过该种情况,通常情况下就是Controller不工作了,导致无法分配leader,那既然知道问题后,解决方案也就很简单了。重启Controller节点上的Kafka进程,让其他节点重新注册Controller角色,但是如上面ZooKeeper的作用,你要知道为什么Controller可以自动注册。
当然了,当你知道controller的注册机制后,你也可以说:删除ZooKeeper节点/controller,触发Controller重选举。Controller重选举能够为所有主题分区重刷分区状态,可以有效解决因不一致导致的 Leader 不可用问题。但是,需要注意的是,直接操作ZooKeeper是一件风险很大的操作,就好比在Linux中执行了rm -rf /xxx一样,如果在/和xxx之间不小心多了几个空格,那”恭喜你”,今年白干了。
炫技式题目
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内部的。
Kafka能手动删除消息吗?
Kafka不需要用户手动删除消息。它本身提供了留存策略,能够自动删除过期消息。当然,它是支持手动删除消息的。
- 对于设置了Key且参数cleanup.policy=compact的主题而言,我们可以构造一条 的消息发送给Broker,依靠Log Cleaner组件提供的功能删除掉该 Key 的消息。
- 对于普通主题而言,我们可以使用kafka-delete-records命令,或编写程序调用Admin.deleteRecords方法来删除消息。这两种方法殊途同归,底层都是调用Admin的deleteRecords方法,通过将分区Log Start Offset值抬高的方式间接删除消息。
__consumer_offsets是做什么用的?
这是一个内部主题,主要用于存储消费者的偏移量,以及消费者的元数据信息(消费者实例,消费者id等等)
需要注意的是:Kafka的GroupCoordinator组件提供对该主题完整的管理功能,包括该主题的创建、写入、读取和Leader维护等。
分区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。
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特性了。
深度思考题
Kafka为什么不支持读写分离?
这其实是分布式场景下的通用问题,因为我们知道CAP理论下,我们只能保证C(可用性)和A(一致性)取其一,如果支持读写分离,那其实对于一致性的要求可能就会有一定折扣,因为通常的场景下,副本之间都是通过同步来实现副本数据一致的,那同步过程中肯定会有时间的消耗,如果支持了读写分离,就意味着可能的数据不一致,或数据滞后。
Leader/Follower模型并没有规定Follower副本不可以对外提供读服务。很多框架都是允许这么做的,只是 Kafka最初为了避免不一致性的问题,而采用了让Leader统一提供服务的方式。
不过,自Kafka 2.4之后,Kafka提供了有限度的读写分离,也就是说,Follower副本能够对外提供读服务。
如何调优Kafka?
作为SRE来讲,任何生产环境的调优,首先需要识别问题和瓶颈点,而不是随意的进行臆想调优。随后,需要确定优化目标,并且定量给出目标。
对于Kafka来讲,常见的调优方向基本为:吞吐量、延时、持久性和可用性,每种目标之前都是由冲突点,这也就要求了,我们在对业务接入使用时,要进行业务场景的了解,以对业务进行相对的集群隔离,因为每一个方向的优化思路都是不同的,甚至是相反的。
确定了目标之后,还要明确优化的维度。有些调优属于通用的优化思路,比如对操作系统、JVM等的优化;有些则是有针对性的,比如要优化Kafka的TPS。我们需要从3个方向去考虑:
- Producer端:增加batch.size和linger.ms,启用压缩,关闭重试
- Broker端:增加num.replica.fetchers提升Follower同步TPS,避免Broker Full GC等。
- Consumer:增加fetch.min.bytes
Controller发生网络分区(Network Partitioning)时,Kafka会怎么样?
这道题目能够诱发我们对分布式系统设计、CAP理论、一致性等多方面的思考。
一旦发生Controller网络分区,那么,第一要务就是查看集群是否出现“脑裂”,即同时出现两个甚至是多个Controller组件。这可以根据Broker端监控指标ActiveControllerCount来判断。
不过,通常而言,我们在设计整个部署架构时,为了避免这种网络分区的发生,一般会将broker节点尽可能的防止在一个机房或者可用区。
由于Controller会给Broker发送3类请求,LeaderAndIsrRequest,StopReplicaRequest,UpdateMetadataRequest,因此,一旦出现网络分区,这些请求将不能顺利到达Broker端。
这将影响主题的创建、修改、删除操作的信息同步,表现为集群仿佛僵住了一样,无法感知到后面的所有操作。因此,网络分区通常都是非常严重的问题,要赶快修复。
Java Consumer为什么采用单线程来获取消息?
在回答之前,如果先把这句话说出来,一定会加分:Java Consumer是双线程的设计。一个线程是用户主线程,负责获取消息;另一个线程是心跳线程,负责向Kafka汇报消费者存活情况。将心跳单独放入专属的线程,能够有效地规避因消息处理速度慢而被视为下线的“假死”情况。
单线程获取消息的设计能够避免阻塞式的消息获取方式。单线程轮询方式容易实现异步非阻塞式,这样便于将消费者扩展成支持实时流处理的操作算子。因为很多实时流处理操作算子都不能是阻塞式的。另外一个可能的好处是,可以简化代码的开发。多线程交互的代码是非常容易出错的。
简述Follower副本消息同步的完整流程
首先,Follower发送FETCH请求给Leader。
接着,Leader会读取底层日志文件中的消息数据,再更新它内存中的Follower副本的LEO值,更新为FETCH请求中的fetchOffset值。
最后,尝试更新分区高水位值。Follower接收到FETCH响应之后,会把消息写入到底层日志,接着更新LEO和HW值。
Leader和Follower的HW值更新时机是不同的,Follower的HW更新永远落后于Leader的HW。这种时间上的错配是造成各种不一致的原因。
因此,对于消费者而言,消费到的消息永远是所有副本中最小的那个HW。