kafka面试整理
1、kafka的选取机制
它负责管理整个集群中所有分区和副本的状态。当某个分区的leader副本出现故障时,由controller负责为该分区选举新的leader副本。当检测到某个分区的ISR集合发生变化时,由controller负责通知所有broker更新其元数据信息。当使用kafka-topics.sh脚本为某个topic增加分区数量时,同样还是由控制器负责分区的重新分配。
(1)控制器选举:控制器负责所有 topic 的分区副本分配和 leader 选举等工作。
1)控制器其实是一个Borker,在kafka集群中,有多个broker节点,但是它们之间需要选举出一个leader,其他的broker充当follower角色。集群中第一个启动的broker会通过在zookeeper中创建临时节点/controller来让自己成为控制器,其他broker启动时也会在zookeeper中创建临时节点,但是发现节点已经存在,所以它们会收到一个异常,意识到控制器已经存在,那么就会在zookeeper中创建watch对象,便于它们收到控制器变更的通知。
2)如果控制器由于网络原因与zookeeper断开连接或者异常退出,那么其他broker通过watch收到控制器变更的通知,就会去尝试创建临时节点/controller,如果有一个broker创建成功,那么其他broker就会收到创建异常通知,也就意味着集群中已经有了控制器,其他broker只需创建watch对象即可。
3)如果集群中有一个broker发生异常退出了,那么控制器就会检查这个broker是否有分区的副本leader,如果有那么这个分区就需要一个新的leader,此时控制器就会去遍历其他副本,决定哪一个成为新的leader,同时更新分区的ISR集合。
4)如果有一个broker加入集群中,那么控制器就会通过Broker ID去判断新加入的broker中是否含有现有分区的副本,如果有,就会从分区副本中去同步数据。
5)集群中每选举一次控制器,就会通过zookeeper创建一个controller epoch,每一个选举都会创建一个更大,包含最新信息的epoch,如果有broker收到比这个epoch旧的数据,就会忽略它们,kafka也通过这个epoch来防止集群产生“脑裂”。
Zookeeper中还有一个与控制器有关的/controller_epoch节点,这个节点是持久(PERSISTENT)节点,节点中存放的是一个整型的controller_epoch值。controller_epoch用于记录控制器发生变更的次数,即记录当前的控制器是第几代控制器,我们也可以称之为“控制器的纪元”。controller_epoch的初始值为1,即集群中第一个控制器的纪元为1,当控制器发生变更时,每选出一个新的控制器就将该字段值加1。每个和控制器交互的请求都会携带上controller_epoch这个字段,如果请求的controller_epoch值小于内存中的controller_epoch值,则认为这个请求是向已经过期的控制器所发送的请求,那么这个请求会被认定为无效的请求。如果请求的controller_epoch值大于内存中的controller_epoch值,那么则说明已经有新的控制器当选了。由此可见,Kafka通过controller_epoch来保证控制器的唯一性,进而保证相关操作的一致性。
(2)分区副本选取leader
1)如果某个分区的Leader挂了,那么其它跟随者follower将会进行选举产生一个新的leader,之后所有的读写就会转移到这个新的Leader上,在kafka中,其不是采用常见的多数选举的方式进行副本的Leader选举,而是会在Zookeeper上针对每个Topic维护一个称为ISR(in-sync replica,已同步的副本)的集合,显然还有一些副本没有来得及同步。只有这个ISR列表里面的才有资格成为leader(先使用ISR里面的第一个,如果不行依次类推,因为ISR里面的是同步副本,消息是最完整且各个节点都是一样的)。
2)kafka中的ISR,OSR,AR
a、ISR:所有与leader副本保持一定程度同步的副本(包括Leader)组成ISR(In-Sync Replicas),ISR集合是AR集合中的一个子集。消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本而言会有一定程度的滞后。
b、OSR:与leader副本同步滞后过多的副本即失效副本;如果与leader通信后,会尝试与leader同步,同步的策略是首先将当前记录的hw之后的消息删除,然后与leader同步,当与leader基本同步之后(存储的消息的offset大于当前isr中的hw),就重新回到isr之中
c、AR:分区中的所有副本统称为AR;AR=ISR+OSR
3)ISR伸缩:leader 副本负责维护和跟踪 ISR 集合中所有 follower 副本的滞后状态,当 follower 副本落后太多或失效时,leader 副本会把它从 ISR 集合中剔除。如果 OSR 集合中有 follower 副本“追上”了 leader 副本,那么 leader 副本会把它从 OSR 集合转移至 ISR 集合。默认情况下,当 leader 副本发生故障时,只有在 ISR 集合中的副本才有资格被选举为新的 leader,而在 OSR 集合中的副本则没有任何机会(不过这个原则也可以通过修改相应的参数配置来改变)。
2、kafka中的LEO与HW
(1)LEO(Log End Offset)和HW(High WaterMark)
1)LEO:每个副本最大的offset
2)HW:消费者能见到的最大的offset,ISR集合中最小的LEO
(2)LEO与HW所解决的问题:解决follower故障和leader故障问题
1)follower故障:follower 发生故障后会被临时踢出 ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。等该 follower 的 LEO 大于等于 该Partition 的 HW(leader的HW),即 follower 追上 leader 之后,就可以重新加入 ISR 了。
2)leader故障:leader 发生故障之后,会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
3)如何解决HW进行数据恢复时可能存在的数据丢失和重复的问题:引入Lead Epoch,(epoch,offset)。epoch表示leader的版本号,从0开始,当leader变更过1次时epoch就会+1,而offset则对应于该epoch版本的leader写入第一条消息的位移。因此假设有两对值:(0, 0),(1, 120),表示第一个leader从位移0开始写入消息;共写了120条[0, 119];而第二个leader版本号是1,从位移120处开始写入消息。leader broker中会保存这样的一个缓存,并定期地写入到一个checkpoint文件中。当leader写底层log时它会尝试更新整个缓存——如果这个leader首次写消息,则会在缓存中增加一个记录;否则就不做更新。而每次副本重新成为leader时会查询这部分缓存,获取出对应leader版本的位移,这就不会发生数据不一致和丢失的情况。
3、kafka是如何体现消息顺序性的?
分区有序,全局无序。
4、消费者提交消费位移时提交的是当前消费到的最新消息的 offset 还是 offset+1?
在旧消费者客户端中,消费位移是存储在 ZooKeeper 中的。而在新消费者客户端中,消费位移存储在 Kafka 内部的topic__consumer_offsets 中。当前消费者需要提交的消费位移是offset+1。
5、有哪些情形会造成重复消费?
(1)重平衡:一个consumer正在消费一个分区的一条消息,还没有消费完,发生了rebalance(加入了一个consumer),从而导致这条消息没有消费成功,rebalance后,另一个consumer又把这条消息消费一遍。
(2)消费者手动提交offset:如果先消费消息,再更新offset位置,导致消息重复消费。
6、哪些情景会造成消息漏消费?
(1)消费者自动提交offset:设置offset为自动定时提交,当offset被自动定时提交时,数据还在内存中未处理,此时刚好把线程kill掉,那么offset已经提交,但是数据未处理,导致这部分内存中的数据丢失。
(2)生产者发送消息设置ack=0:它只管往 Kafka 中发送消息而并不关心消息是否正确到达。不过在某些时候(比如发生不可重试异常时)会造成消息的丢失。这种发送方式的性能最高,可靠性也最差。
7、kafka的topic问题
(1)kafka-topics.sh 创建(删除)了一个 topic 之后,Kafka 背后会执行什么逻辑?
1)会在 zookeeper 中的/brokers/topics 节点下创建一个新的 topic 节点,如:/brokers/topics/first 该节点中记录了该主题的分区副本分配方案
2)触发 Controller 的监听程序 ,kafka Controller 负责 topic 的创建工作,并更新元数据信息。
(2)topic的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
可以增加,当分区数增加时,就会触发订阅该主题的所有 Group 开启 Rebalance。首先,Rebalance过程中,所有 Consumer 实例都会停止消费,等待 Rebalance 完成。然后所有 Consumer 实例共同参与,全部重新分配所有分区。其实更高效的做法是尽量减少分配方案的变动。
(3)topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
不支持,因为删除的分区中的消息不好处理。如顺序性问题、事务性问题,以及分区和副本的状态机切换问题。
8、Kafka 生产者客户端的整体结构是什么样子的?使用了几个线程来处理?分别是什么?
(1)整个生产者客户端由两个线程协调运行,这两个线程分别为主线程和 Sender 线程(发送线程)。在主线程中由 KafkaProducer 创建消息,然后通过可能的拦截器、序列化器和分区器的作用之后缓存到消息累加器(RecordAccumulator,也称为消息收集器)中。Sender 线程负责从 RecordAccumulator 中获取消息并将其发送到 Kafka 中。
(2)kafka的分区器,序列化器,拦截器作用,以及这三者的使用顺序。
1)分区器是指定哪一条消息进入哪一个分区,序列化器是对消息进行序列化,方便网络传输。
2)生产者拦截器既可以用来在消息发送前做一些准备工作,比如按照某个规则过滤不符合要求的消息、修改消息的内容等,也可以用来在发送回调逻辑前做一些定制化的需求,比如统计类工作。消费者拦截器主要在消费到消息或在提交消费位移时进行一些定制化的操作。
3)使用顺序:拦截器,序列化器,分区器。
8、Kafka 的日志目录结构或者存储机制
(1)存储机制:Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向 topic的。
topic 是逻辑上的概念,而 partition 是物理上的概念,每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。 数据存储示意图1如下,index 和 log 文件如图2
图1 图2
1)对图1的解释:由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。每个 segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称+分区序号。例如,first 这个 topic 有三个分区,则其对应的文件夹为 first-0,first-1,first-2。
2)对图2的解释:index 和 log 文件以当前 segment 的第一条消息的 offset 命名。下图为 index 文件和 log 文件的结构示意图。其中 “.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元 数据指向对应数据文件中 message 的物理偏移地址。
9、kafka如何设置生存周期与清理数据
启动一个后台线程定期扫描log file列表,把保存时间超过阀值的文件直接删除(根据文件的创建时间).清理参数在server.properties文件中。
10、如何实现消费已经被消费过的数据:启动另外一组不同消费者组
11、zookeeper如何管理kafka
(1)Producer端使用zookeeper用来"发现"broker列表,以及和Topic下每个partition leader建立socket连接并发送消息。
(2)Broker端使用zookeeper用来注册broker信息,以及监测partition leader存活性。
(3)Consumer端使用zookeeper用来注册consumer信息,其中包括consumer消费的partition列表等,同时也用来发现broker列表,并和partition leader建立socket连接,并获取消息。
11、kafka的高性能设计
(1)写入数据的高性能设计
Kafka的消息是保存或缓存在磁盘上的,但是在磁盘上读写数据性能较低,因为寻址会比较消耗时间。为优化写入速度Kafka采用了两个技术: 顺序写入和MMFile 。
1)顺序写入:磁盘读写的快慢取决于采用顺序读写还是随机读写。在顺序读写的情况下,磁盘的顺序读写速度较快。磁盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动作”,比较耗时。为了提高读写硬盘的速度,Kafka使用顺序磁盘I/O。Kafka收到生产者的消息会把数据插入到文件末尾。
a、Linux磁盘操作的优化:read-ahead和write-behind,磁盘缓存。
1.read-ahead预读:可以有效的减少磁盘的寻 道次数和应用程序的I/O等待时间。
2.write-behind:先缓存,后写操作
b、磁盘操作的好处:
1.顺序写入磁盘顺序读写速度超过内存随机读写
2.顺序写入JVM的GC效率低,内存占用大。使用磁盘可以避免这一问题
3.顺序写入系统冷启动后,磁盘缓存依然可用。
c、Kakfa提供了两种策略来删除数据:基于时间;基于partition文件大小。
2)Memory Mapped Files
顺序写入硬盘,硬盘的访问速度还是不可能追上内存。所以Kafka的数据并不是实时的写入硬盘 ,利用了操作系统分页存储来利用内存提高I/O效率。Memory Mapped Files即内存映射文件利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后操作系统在适当的时候对物理内存的操作会被同步到硬盘上。
(2)读取数据的高性能操作
1)零复制:传统的文件传输:硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎;零拷贝技术”只用将磁盘文件的数据复制到页面缓存中一次,然后将数据拷贝到socket
缓冲区中,最后直接发送到网络中(发送给不同的订阅者时,都可以使用同一个页面缓存),避免了重复复制操作。
a、sendfile系统调用,文件数据被copy至内核缓冲区
b、再从内核缓冲区copy至内核中socket相关的缓冲区
c、最后再socket相关的缓冲区copy到协议引擎
2)批量压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩。 压缩的好处就是减少传输的数据量,减轻对网络传输的压力。
3)分区。