Kafka的相关知识点一直都是大数据面试中的热门考点,那么在面试中,我们需要储备哪些必备的知识呢?下面小编就以现场面试的形式来介绍一下Kafka中必知必会的知识点。
1 kafka 基础架构考察
1.1 面试官 :看你的简历上写了对Kafka有一定了解,那么请简单介绍一下Kafka吧。
通常,面试官对一个知识点感兴趣的时候,会让我们介绍一下相关知识,例如这次要介绍的Kafka,这里主要考察的是对Kafka中的一些基本概念的理解。我们可以从Kafka的概念,应用场景和架构进行介绍。
1.1.1 Kafka的概念
Kafka是Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写,它是一种分布式的发布-订阅消息系统,在Kafka集群中,没有“中心主节点”的概念,集群中所有的服务器都是对等的,因此,可以在不做任何配置的更改的情况下实现服务器的的添加与删除,同样的消息的生产者和消费者也能够做到随意重启和机器的上下线。
1.1.2 Kafka的应用场景
Kafka是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。我们以流量削峰为例,介绍一下kafka的应用场景。
在秒杀活动中,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。这样既可以控制活动的人数,又可以缓解短时间内高流量压垮应用,用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。秒杀业务根据消息队列中的请求信息,再做后续处理,Kafka就充当了消息队列这么一个角色。
1.1.3 Kafka的架构
Kafka集群以Topic的形式分类集群中的Record,每个Record对应一个Topic。每个Topic底层对应一组分区的日志以持久化Topic中的Record。同时在Kafka中,每一个分区都一定会有一个broker担当这个分区的leader,其他Broker担当该分区的follower,leader负责读写操作,follower负责同步该分区的数据,如果leader所在的broker宕机,该分区的其他follower会选举出一个新的leader继续负责该分区的读写操作。其中leader的监控和Topic的部分元数据存放在Zookeeper中。
Kafka集群中的主要角色:
-
Producer:消息的产生的源头,负责选择将哪个记录分发到哪个Topic中的哪个Partition中。
-
Consumer:消息的使用方,负责消费Kafka服务器上的消息。
-
Topic:由用户定义并配置在Kafka服务器,用于建立生产者和消息者之间的订阅关系:生产者发送消息到指定的Topic下,消息者从这个Topic下消费消息。
-
Partition:分区是一个有序且不可变的序列,分区中每一个Record都被分配了一个唯一的序列编号,称为offset,Kafka集群会持久化所有发布到Topic中的Record信息,持久化时间由配置文件决定,默认为168小时(
log.retention.hours
)。 -
Broker:Kafka的服务器,Kafa集群中的一台或多台服务器统称为 broker。
1.2 面试官:你刚刚提到了Topic中的Partition是分区有序的,那么它可不可以实现全局有序呢?如果可以,该怎么做?
这个问题看似平平无奇,其实暗藏杀机,因为在多分区的情况下,Topic是无法保证全局有序的。所以这个问题,我们首先要回答"多分区的情况下无法保证全局有序"。但是事实上我们是可以做到全局有序的,这个做法有些极端,就是只设一个分区,这样分区内部有序就等同于全局有序了(当然这样会可能造成数据丢失),还有一种做法,就是对特殊的消息,修改producer的分发策略,将所有这种类型的消息发送到同一个partition上去消费,这样间接实现了单一类型消息的全局有序。
2 kafka 特性考察
2.1 面试官:说一说Kafka为什么这么快
这个问题考察的是Kafka的高吞吐量的特性,Kafka的保存或缓存在磁盘上的,一般认为在磁盘上进行数据读写是会降低性能的,但即便是普通的服务器,Kafka也能轻松支持百万级的数据写入请求,超过了大部分的消息中间件。那么它是如何做到的呢?为了优化写入速度,Kafka采用了3个技术:顺序写入、MMFile(Memory Mapped Files)和零拷贝,我们可以从这两个方面来回答。
2.1.1 顺序写入
我们知道,硬盘是机械结构,每次读写都会经历寻址和写入两个过程,其中寻址是一个机械动作,它是最耗时的,所以要提高读写速度,最直接的方法就是使用顺序写入,Kafka使用的计算顺序I/O,这样省去了大量的内存开销(因为在寻址过程中需要把数据缓存起来,这样会占用大量内存)并节省了I/O的寻址时间。但是单纯地使用顺序读写,Kafka的写入性能也不能和内存相比,所以Kafka还充分利用了现代操作系统的分页存储技术(Page Cache)来利用内存提高I/O效率。
2.1.2 Memory Mapped Files
Memory Mapped Files也称为内存映射文件,在64位操作系统中一般可以表示20G的数据文件,它的工作原理是直接利用内存的Page来实现文件到物理内存的直接映射,完成MMP映射后,用户对内存的所有操作会被操作系统刷新到磁盘上,Page Cache技术尽可能地利用了系统中的空闲内存,极大地降低了I/O使用率。
2.1.3 零拷贝(Zero Copy)
前两个技术是生产者端使用的技术,而零拷贝是消费者端使用的技术,在介绍零拷贝之前,我们先来了解一下传统的网络I/O流程,传统的网络I/O流程主要分为以下4步:
- OS从硬盘把数据读到内核区的PageCache。
- 用户进程把数据从内核区Copy到用户区。
- 然后用户进程再把数据写入到Socket,数据流入内核区的Socket Buffer上。
- OS再把数据从Buffer中Copy到网卡的Buffer上,这样完成一次发送。
整个过程共经历两次Context Switch,四次System Call,且CPU参与了多次I/O中断。同一份数据在内核Buffer与用户Buffer之间重复拷贝,效率低下。其中2、3两步没有必要,完全可以直接在内核区完成数据拷贝。这也正是零拷贝所解决的问题,经过零拷贝优化后,整个I/O过程就变成了下面这个样子。
零拷贝的流程主要分为3步:
- 文件从磁盘中被拷贝到内核缓冲区。
- 从内核缓冲区copy到socket缓冲区。
- 数据从socket缓冲区copy到相关协议引擎并发送出去。
2.2 面试官:Kafka中的数据是如何保持同步的?
这题考察的是Kafka的高可用的特性,关于这个问题,可以从Kafka 0.11
版本之前的解决方案和Kafka 0.11
之后的解决方案来回答。Kafka 0.11版本之前,使用High Watermark(HW)机制来保证数据的同步,从Kafka 0.11开始,使用leader epoch来保证数据的同步。
2.1.1 HW机制
首先,Kafka的数据同步有以下概念:
- LEO:log end offset,标识每个分区中最后一条记录的下一个位置,每个分区都有自己的LEO。
- HW:即上面提到的水位值。对于同一个副本对象而言,其HW值不会大于LEO值。小于等于HW值的所有消息都被认为是“已备份”的(replicated)。
- ISR副本:包含了leader副本和所有与leader副本保持同步的follower副本。
上图中,HW值是7,表示位移是0-7的所有消息都已经处于“已备份状态”(committed),而LEO值是15,那么8~14的消息就是尚未完全备份(fully replicated)—为什么没有15?因为刚才说过了,LEO指向的是下一条消息到来时的位移,故上图使用虚线框表示。我们总说consumer无法消费未提交消息。这句话如果用以上名词来解读的话,应该表述为:consumer无法消费分区下leader副本中位移值大于分区HW的任何消息。这里需要特别注意分区HW就是leader副本的HW值。
我们可以把它想象成是一个木桶,木桶的容量取决于最短的那条木板的长度,同样的,HW的值也取决于各follower的LEO的最小值。【关于HW同步机制可以讲很多,如果要展开讲的话可以参考这篇博文】
2.1.2 leader epoch机制
由于HW机制可能导致数据丢失和数据不一致的情况,所以Kafka 0.11版本采用了leader epoch机制。Leader端多开辟一段内存区域专门保存leader的epoch信息,这样即使出现上面的两个场景也能很好地规避这些问题。所谓leader epoch实际上是一对值:(epoch,offset)。epoch表示leader的版本号,从0开始,当leader变更过1次时epoch就会+1,而offset则对应于该epoch版本的leader写入第一条消息的位移。
- 规避数据丢失
上图左半边已经给出了简要的流程描述,这里不详细展开具体的leader epoch实现细节(比如OffsetsForLeaderEpochRequest的实现),我们只需要知道每个副本都引入了新的状态来保存自己当leader时开始写入的第一条消息的offset以及leader版本。这样在恢复的时候完全使用这些信息而非水位来判断是否需要截断日志。 - 规避数据不一致
同样的道理,依靠leader epoch的信息可以有效地规避数据不一致的问题。
3 kafka 数据完整性考察
3.1 面试官:说一下Kafka是如何保证消息队列的数据不丢失的
这个问题考察的是数据的完整性,发生消息丢失的情况可能是生产者丢失,也可能是消费者丢失,我们可以从生产者保证数据不丢失和消费者保证数据不丢失来回答。
3.1.1生产者保证数据不丢失
生产者数据丢失原因:producer将消息发生给broker时,broker还没来得及备份就突然宕机,导致消息丢失。
解决办法:采用ACK应答机制来避免此类情况发生,producer在向broker发送消息后,要求broker在规定时间内做出应答,否则视为本条消息发送失败,会触发retry机制重新发送消息,应答配置ack的值有3种:
- ack=1:Leader会将消息同步到follower中,但不会等待所有follower都同步完成,leader收到就视为消息发送成功,此时若leader收到消息后立即宕机,就会发生数据丢失。
- ack=0:生产者根本不会等待broker的任何确认,消息发送后立即被视为已发送,这种情况可能出现数据丢失。
- ack=-1(all):leader会等待所有follower都同步完成,才返回确认信息,此时只要还有一台follower处于活跃状态,就能保证记录不丢失,这是最有力的保证。
3.1.2 消费者保证数据不丢失
消费者数据丢失的原因:在Kafka中,由消费者自己来维护消费的offset,在消费过程中,消费者先更新offset,再完成消费,所以假如消费者更新完offset,还没来得及消费就宕机了,当重启后,消费者读取offset,认为已经消费了这条消息,此时就发生了数据丢失。
解决办法:关闭自动提交offset,改为完成消费之后手动提交offset。
3.2 面试官:你刚刚提到了消息的重新发送,那么是否有可能发送数据重复写入的情况呢?
这个问题考察的是Kafka的幂等性,Kafka 0.11.0
版本中增加了幂等的支持,幂等是针对生产者角度的特性,因此我们应该从幂等写的角度去回答。
3.2.1 幂等性
HTTP/1.1对幂等性的定义是:一次和多次请求同一个资源对于资源本身应该具有同样的结果。也就是说,任意多次的执行对资源本身所产生的的影响与一次执行的结果是一样的。
幂等又称为exactly once,要停止多次处理同一条数据,必须仅将其持久化到Topic中仅一次,每一次写请求时,Record都会有一个唯一的单调自增的序列号,在初始化期间,Kafka会给每一个Record一个唯一的ID,这个ID和序列号和消息捆绑在一起发送给broker,由于序列号是自增且单调递增的,所以仅当消息的序列号比最后提交成功的写请求中的序列号大1时,才接收这条消息,否则就认定是生产者重复发送的数据。
注意:在使用幂等性的时候,必须要开启retries=true,并设acks = all。
3.3 面试官:幂等性只能保证单条消息发送的原子性,如果我要保证多条消息的完整性呢?
这个问题考察的是Kafka的事务,Kafka 0.11版本处理引入了幂等的概念,也引入了事务的概念,因此我们需要从事务的角度去回答问题。
3.3.1 事务
Kafka的事务分为生产者only事务,生产者&消费者事务,一般来说,默认消费者的事务级别是read_uncommitted,这有可能读取到事务失败的数据,所以在开启生产者事务之后,需要设置消费者的事务级别为read_committed。
开启生产者的事务,只需要指定transaction.id
即可,一旦开启事务,就默认开启了幂等性,但是要求transaction.id
必须是唯一的,同一时刻只能有一个transaction.id
存在。
开启事务操作之后,如果一批消息发送的过程中,其中一条消息发送失败,那么这批消息都会发送失败,这样就保证了多条消息发送的原子性。
4 kafka 与其它工具的组合使用
4.1 面试官:为什么kafka通常与flume组合使用呢?
这个问题不仅考察了对kafka使用场景的了解程度,而且考察了对flume的理解。为什么要组合使用呢?我们可以从flume的缺点和kafka的优点来回答这个问题。
4.1.1 flume的缺点
flume是一个分布式,可靠且可用的系统,用于高效地收集,聚合大量日志数据并将其从许多不同的源移动到集中式数据存储中。可用于传输大量事件数据。
(1) flume将消息缓存在内存中,一旦发生宕机或者被take掉,这条消息就就会被删除,就可能造成数据丢失的问题,而kafka将数据持久化到磁盘中,基本不会发生数据丢失,规避了flume所带来的风险。
(2) flume只是一个消息传输工具,如果采集到的信息要被多个系统消费,试想,所有的系统都来找flume要数据,势必会对flume产生巨大压力,而kafka的发布-订阅模式完美解决了这个问题。
(3) Kafka和Flume都是可靠的系统,通过适当的配置能保证零数据丢失。然而,Flume不支持副本事件。于是,如果Flume代理的一个节点崩溃了,即使使用了可靠的文件管道方式,你也将丢失这些事件,直到你恢复这些磁盘。如果你需要一个高可靠行的管道,那么使用Kafka是个更好的选择。