Kafka 常见面试题

2022-08-20  本文已影响0人  笔头还没烂
1. Kafka 消息队列的一次性语义

问题1: 什么是一次性语义?
at-most-once:至多一次【允许为0次,可能数据丢失】
at-least-once:至少一次【至少一次,允许多次,可能数据重复】
exactly-once:有且仅有一次【只有一次,精准数据传输】

问题2:Kafka 如何保证生产一次性语义?
数据丢失场景:生产者将数据发送给Kafka,数据在网络传输过程中可能丢失
ACK + 重试机制:生产者生产数据写入Kafka,等待Kafka返回ack确认,收到ack,生产者发送下一条
ACK机制:acks = 0/1/all/-1
0:不等待ack,直接发送下一条
优点:快;
缺点:数据易丢失
1:生产者将数据写入Kafka,Kafka 等待这个分区的 Leader 副本返回ack,发送下一条
优点:性能和安全做了中和的选项;
缺点:依旧存在一定概率的数据丢失的情况
all/-1:生产者将数据写入Kafka,Kafka 等待这个分区所有ISR 【可用】副本同步成功才返回ack,发送下一条
优点:安全
缺点:性能比较差
问题:如果ISR中只有leader一个,leader写入成功,直接返回,leader故障数据丢失怎么办?
解决:搭配min.insync.replicas来使用,并且 >= 2
min.insync.replicas:表示最少要有几个ISR的副本

重试机制

retries = 3 #发送失败的重试次数

数据重复的情况:ACK丢失
Step1 : 生产发送一条数据 A 给 Kafka
Step2 :Kafka 存储数据 A,返回Ack 给生产者
Step3 :如果ack丢失,生产者没有收到 ack,超时,生产者认为数据丢失没有写入 Kafka
Step4 : 生产者基于重试机制重新发送这条数据 A,Kafka 写入数据 A,返回 ack
Step5 : 生产者收到 ack ,发送下一条B

问题: A 在 Kafka 中写入两次,产生数据重复的问题
解决:
实现:生产者在生产数据的过程中会在每条数据中增加一个数据id,当前这一条数据会比上一条数据id多1 。由Kafka服务端进行判断,会根据id进行判断是否写过该数据:
如果没有写入:写入Kafka;
如果已经写入:直接返回ack.
幂等机机制:一个操作被执行多次,结果是一致的:f(x)=f(f(x))

问题3: Kafka 如何保证消费一次性语义?
规则:消费者是根据 offset 来持续消费,只要保证任何场景下消费者都能知道这个分区的 commit offset,并且严格按照 commit offset 来消费即可
问题:commit offset 每个消费者只保存在自己的内存中,如果消费者一旦故障,这个分区的 commit offset 会丢失。
需要:将每个分区的 commit offset 存储在一种可靠的外部存储中,手动管理offset
实现:
step1 : 第一次消费根据属性进行消费
step2 : 消费分区数据,处理分区数据
step3 : 如果处理成功,将处理成功的分区的 Offset 进行额外的存储;
step4 : 如果消费者故障,可以从外部存储读取上一次消费的 offset 向 Kafka 进行请求

小结:生产的不丢失,靠它的 ack 和重试机制;生产的不重复,靠它的幂等性机制,增加数据id;消费的不丢失不重复,靠它的 offset 持久化管理来实现。

问题4: Kafka 是基于磁盘的日志消息队列系统,为什么读写速度那么快?
首先,为什么“写数据”很快?:Kafka 是先将数据写入内存 PageCache页缓存【操作系统级别的缓存】,当满足一定的条件时【如:内存写入超过30秒或者超过内存10%的占用比例的阈值等情况】,此时(可以由操作系统控制,也可以由Kafka 调用操作系统来实现)将数据以追加的方式顺序写入磁盘【顺序写入磁盘会节省很多内存寻址的时间,比随机写入磁盘性能要快得多。数据顺序写磁盘的速度可以媲美随机写内存的速度】。
接着是“读”,为什么“读数据”也很快?:Kafka 先从内存 PageCache 页缓存中读取数据。当生产者生产数据的速度与消费者消费数据的速度相匹配的情况下,数据一写到内存,消费者立马就能消费到,这样数据的消费就不用经过磁盘,达到一个高性能的特点;当然,在一般情况下,生产者的速度与消费者的速度是不一致的,这时候数据就会从内存中被写入到磁盘,即产生数据积压。消费者需要从磁盘中获取数据。Kafka 利用 Segment 设计对磁盘中的数据进行划分。通过稀疏索引加快查询的效率,同时采用零拷贝的机制,可以实现磁盘中的数据不经过任何拷贝地放到内存中来供消费者进行读取。
还有一些机制,比如mmap机制。通过mmap机制,Kafka 构建了内存到磁盘文件的一个映射,将数据写入内存即写入磁盘等。

问题5 :为什么Kafka 采用稀疏索引而不采用全局索引?
全局索引是每一条数据都有对应的索引,当数据量很大的情况下,采用全局索引,会导致索引文件变得很大,检索数据的过程就会变得非常慢;用稀疏索引,能减小索引文件的大小,可以很快地加载到索引文件,同时也能缩小检索范围,从而提高检索效率。当然稀疏索引只能查到离某条数据索引最近的一个范围,不能精准地找到某条数据。读取数据的时候,需要在这个范围的数据读取对应的数据。

2.Kafka 分区副本的概念

问题1: Kafka 如何保证分区数据安全?
分区副本机制:每个 Kafka 中分区都可以构建多个副本,相同分区的副本存储在不同的节点上
为了保证安全和写的性能:划分了副本角色
leader 副本:对外提供读写数据
follower副本:与leader 同步数据,如果 leader 故障,选举一个新的 leader
选举实现: Kafka 主节点 Controller 实现

问题2: 什么是AR、ISR、OSR
AR : All-Replicas 所有副本,指的是一个分区在所有节点上的副本;
ISR:In - Sync- Replicas可用副本,所有正在与Leader 同步的 Follower 副本;
列表中:按照优先级排列【Controller根据副本同步状态以及Broker健康状态】,越靠前越会成为leader副本
如果leader 故障,是从ISR列表中选择新的 leader
如果生产者写入数据:ack=all,写入所有ISR列表中的副本,就返回ack
OSR: Out-Sync-Replicas 不可用副本,长时间没有与leader 副本同步数据的 follower 副本
原因:网络故障等外部环境因素,某个副本与Leader副本的数据差异性很大;
判断是否是一个OSR副本?

replica.lag.time.max.ms = 10000  #可用副本的同步超时时间
// lag 是积压,即数据的积压,即还有多少数据未同步

写入、Leader 选择都只是ISR列表中选取

小结:
关系: AR = ISR + OSR

问题3:什么是 HW、LEO ?
LW : 【分区概念】low_watermark 最低消费的 offset,一般为0,消费者能够消费到的最小的 offset;
HW : 【分区概念】hight_watermark :最高消费的offset,消费者只能消费到这个位置;即当前这个分区所有副本同步的最低位置 + 1,消费者能消费到的最大位置
LSO :【副本概念】Log StartOffset 起始 Offset ,一般为 0,所以一般与 LW 相等
LEO :【副本概念】Log End Offset 下一个待写的 offset = 当前已有的最新 offset + 1
一个分区所有的副本的最小 LEO = 这个分区的 HW

LSO、HW、LEO 之间的关系.png
如上图:
(1)该分区副本可以消费的起始位置为LSO,一般为0.
(2)该分区副本一共写入了9条数据【0~8】,下一次即将写入的位置为9,所以 LEO 为 9
(3)消费者可以消费的区间为 0 ~ 5,则 HW 为 6.【可以理解为最高消费的位置】

小结:
(1)为什么会有 HW 和 LEO?因为 Leader 写入数据,Follower 在同步数据的过程中是可能有延迟的。当 Leader 已经写入了9条数据,Follower 有可能只同步了6条,则此时 HW 为6 , LEO 为 9。
(2)消费者消费这个分区的 HW = 这个分区所有副本中的最小的 LEO = Follow 同步最少的之前的数据。
(3)每当 follower 同步成功以后会返回一个ack 给 leader,leader 收到 ack ,会更新 HW,并提供对外读写。而 Follower 只负责不断跟 leader 同步数据,当 Leader 故障,Follower 才会选举成新的 Leader。
(4)LEO 表示这个分区所有的副本同步以后,下一个待写的位置;HW 这个分区消费者能够消费到的最小 LEO,来保证消费的一致性。

3. 其他面试题

问题1: 什么是 CAP 理论,Kafka 满足哪两个?
C : 一致性,任何一台机器写入数据,其他节点也可以读取到;
A :可用性,如果一个节点故障,其他节点可以正常提供数据服务;
P :分区容错性,如果某个分区故障,这个分区的数据照样可用
Kafka 满足CA,Kafka 不能保证一个分区故障,这个分区的数据照样可用。

问题2: Canal 实时采集 MySQL,将 MySQL 中实时的数据写入 Kafka 的时候,消费端消费的更新日志在插入日志之前,就会因为数据缺失导致异常,怎么保证插入和更新的顺序?
栗子:在 MySQL 做了两个操作,一个是insert,一个是 update 操作,这两个操作中间间隔的时间很短。
根本原因:Kafka 多个分区不能保证顺序,只能保证分区内部顺序;
解决:
(1)方案一:只构建一个分区;【性能太差,一定是不选用】
(2)方案二:将数据库和表名作为 key,然后按照 Hash 取余规则写入 Kafka【即保证同一个数据同一张表的操作写入同一个分区】
(3)方案三: Flink 基于事件时间处理来实现

问题3: timeindex的功能是什么?
Kafka 存储提供了两种索引方式
index: offset 位移索引
timeindex:时间索引
Kafka 提供了两种条件消费数据的方式
方式一:提供 Topic、Partition、Offset 【默认方式】
方式二: 提供 Topic、Partiton、time
(1)代码中需要指定分区消费,以及指定每个分区消费的时间

consumer.seek(分区,分区要消费的offset)

(2)只能指定offset,如果要用时间,就需要将时间转换为offset

//将每个分区要消费的时间转换为offset
Map<TopicPartiton,offset> offset = consumer.offsetForTimes(topicPartitionLongMap);

(3)上面第二小步要求必须传入每个分区从什么时间消费的 Map 集合,例如想把昨天的数据重新消费一遍,代码如下:

// 构建一个空的 Map 集合
Map<TopicPartiton,Long> topicPartitionLongMap = new HashMap<>;
// 获取当前这个消费者消费的所有分区
Set<TopicPartiton> partitions = consumer.assignment();
//添加每个分区要从什么时间消费
for(TopicPartition part: partitions) {
   topicPartitionLongMap.put(part,System.currentTimeMillis() - 1 * 24 * 3600000)
}

第二种方式的本质还是第一种方式:根据上一次消费的时间去获取对应时间范围内的 offset,再消费 offset
如何知道上一次消费到的当前时间的 offset 是哪些?
(1)检索 timeindex 文件:你想要的这个时间对应的 offset 是这个 Segment 文件中的第几条
(2)检索 index 文件:根据第几条找到对应的offset 的物理偏移量
(3)读取 log 文件:根据时间来做比较,找到这个时间对应的数据的 offset.

上一篇下一篇

猜你喜欢

热点阅读