可靠性与消费 - Kafka 面试题及答案
11.Kafka 的可靠性是怎么保证的?

1.acks
这个参数用来指定分区中有多少个副本收到这条消息,生产者才认为这条消息是写入成功的,这个参数有三个值:
1.acks = 1,默认为1。生产者发送消息,只要 leader 副本成功写入消息,就代表成功。这种方案的问题在于,当返回成功后,如果 leader 副本和 follower 副本还没有来得及同步,leader 就崩溃了,那么在选举后新的 leader 就没有这条消息,也就丢失了。
2.acks = 0。生产者发送消息后直接算写入成功,不需要等待响应。这个方案的问题很明显,只要服务端写消息时出现任何问题,都会导致消息丢失。
3.acks = -1 或 acks = all。生产者发送消息后,需要等待 ISR 中的所有副本都成功写入消息后才能收到服务端的响应。毫无疑问这种方案的可靠性是最高的,但是如果 ISR 中只有leader 副本,那么就和 acks = 1 毫无差别了。
2.消息发送的方式
第6问中我们提到了生产者发送消息有三种方式,发完即忘,同步和异步。我们可以通过同步或者异步获取响应结果,失败做重试来保证消息的可靠性。
3.手动提交位移
默认情况下,当消费者消费到消息后,就会自动提交位移。但是如果消费者消费出错,没有进入真正的业务处理,那么就可能会导致这条消息消费失败,从而丢失。我们可以开启手动提交位移,等待业务正常处理完成后,再提交offset。
4.通过副本 LEO 来确定分区 HW
可参考第五问
12.Kafka 的消息消费方式有哪些?
一般消息消费有两种模式,推和拉。Kafka的消费是属于拉模式的,而此模式的消息消费方式有两种,点对点和发布订阅。 
- 1.点对点:如果所有消费者属于同一个消费组,那么所有的消息都会被均匀的投递给每一个消费者,每条消息只会被其中一个消费者消费。

- 2.发布订阅:如果所有消费者属于不同的消费组,那么所有的消息都会被投递给每一个消费者,每个消费者都会收到该消息。
13.分区再分配是做什么的?解决了什么问题?
分区再分配主要是用来维护 kafka 集群的负载均衡
既然是分区再分配,那么 kafka 分区有什么问题呢?

问题1:当集群中的一个节点下线了
如果该节点的分区是单副本的,那么分区将会变得不可用
如果是多副本的,就会进行 leader 选举,在其他机器上选举出新的 leader
kafka 并不会将这些失效的分区迁移到其他可用的 broker 上,这样就会影响集群的负载均衡,甚至也会影响服务的可靠性和可用性

- 问题2:当集群新增 broker 时,只有新的主题分区会分配在该 broker 上,而老的主题分区不会分配在该 broker 上,就造成了老节点和新节点之间的负载不均衡。
为了解决该问题就出现了分区再分配,它可以在集群扩容,broker 失效的场景下进行分区迁移。
分区再分配的原理就是通化控制器给分区新增新的副本,然后通过网络把旧的副本数据复制到新的副本上,在复制完成后,将旧副本清除。 当然,为了不影响集群正常的性能,在此复制期间还会有一些列保证性能的操作,比如复制限流。
14.副本 leader 是怎么选举的?
当分区 leader 节点崩溃时,其中一个 follower 节点会成为新的 leader 节点,这样会导致集群的负载不均衡,从而影响服务的健壮性和稳定性。
如下:
Topic: test Partation:0 Leader:1 Replicas:1,2,0 Isr:1,2,0 Topic: test Partation:1 Leader:2 Replicas:2,0,1 Isr:2,0,1 Topic: test Partation:2 Leader:0 Replicas:0,1,2 Isr:0,1,2
我们可以看到
0 分区有 1 个 leader
1 分区有 2 个 leader
2 分区有 0 个 leader
如果此时中间的节点重启
Topic: test Partation:0 Leader:1 Replicas:1,2,0 Isr:1,0,2 Topic: test Partation:1 Leader:0 Replicas:2,0,1 Isr:0,1,2 Topic: test Partation:2 Leader:0 Replicas:0,1,2 Isr:0,1,2
我们又可以看到:
0 分区有 1 个 leader
1 分区有 0 个 leader
2 分区有 0 个 leader
我们会发现,原本 1 分区有两个 ledaer,经过重启后 leader 都消失了,如此就负载不均衡了。
为了解决这种问题,就引入了优先副本的概念
优先副本就是说在 AR 集合中的第一个副本。比如分区 2 的 AR 为 0,1,2,那么分区 2 的优先副本就为0。理想情况下优先副本就是 leader 副本。优先副本选举就是促使优先副本成为 leader 副本,从而维护集群的负载均衡。
15.分区数越多越好吗?吞吐量就会越高吗?
一般类似于这种问题的答案,都是持否定态度的。
但是可以说,在一定条件下,分区数的数量是和吞吐量成正比的,分区数和性能也是成正比的。
那么为什么说超过了一定限度,就会对性能造成影响呢?原因如下:

1.客户端/服务器端需要使用的内存就越多
服务端在很多组件中都维护了分区级别的缓存,分区数越大,缓存成本也就越大。
消费端的消费线程数是和分区数挂钩的,分区数越大消费线程数也就越多,线程的开销成本也就越大
生产者发送消息有缓存的概念,会为每个分区缓存消息,当积累到一定程度或者时间时会将消息发送到分区,分区越多,这部分的缓存也就越大
2.文件句柄的开销
每个 partition 都会对应磁盘文件系统的一个目录。在 Kafka 的数据日志文件目录中,每个日志数据段都会分配两个文件,一个索引文件和一个数据文件。每个 broker 会为每个日志段文件打开一个 index 文件句柄和一个数据文件句柄。因此,随着 partition 的增多,所需要保持打开状态的文件句柄数也就越多,最终可能超过底层操作系统配置的文件句柄数量限制。
3.越多的分区可能增加端对端的延迟
Kafka 会将分区 HW 之前的消息暴露给消费者。分区越多则副本之间的同步数量就越多,在默认情况下,每个 broker 从其他 broker 节点进行数据副本复制时,该 broker 节点只会为此工作分配一个线程,该线程需要完成该 broker 所有 partition 数据的复制。
4.降低高可用性
在第 13 问我们提到了分区再分配,会将数据复制到另一份副本当中,分区数量越多,那么恢复时间也就越长,而如果发生宕机的 broker 恰好是 controller 节点时:在这种情况下,新 leader 节点的选举过程在 controller 节点恢复到新的 broker 之前不会启动。controller 节点的错误恢复将会自动地进行,但是新的 controller 节点需要从 zookeeper 中读取每一个 partition 的元数据信息用于初始化数据。例如,假设一个Kafka 集群存在 10000个partition,从 zookeeper 中恢复元数据时每个 partition 大约花费 2 ms,则 controller 的恢复将会增加约 20 秒的不可用时间窗口。
16.如何增强消费者的消费能力?
1.可以考虑增加 topic 的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。
2.如果是消费者消费不及时,可以采用多线程的方式进行消费,并且优化业务方法流程,同样的分区数,为什么人家并发那么高,你的就不行??
17.消费者与 topic 的分区分配策略有哪些?

1.RangeAssignor 分配策略
该分配策略是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后分区按照跨度来进行平均分配,尽可能保证分区均匀的分配给所有的消费者。
对于每个 topic,该策略会讲消费者组内所有订阅这个主题的消费者按照名称的字典顺序排序,然后为每个消费者划分固定过的区域,如果不够平均分配,那么字典排序考前的就会多分配一个分区。
比如 2 个消费者属于一个消费者组,有 2 个 topic t1,t2,每个 topic 都有 3 个分区,p1,p2,p3,那么分配的情况如下:
消费者A:t0-p0,t0-p1,t1-p0,t1-p1, 消费者B:t0-p2,t1-p2
这样就会出现非配不均匀的情况
2.RoundRobinAssignor 分配策略
该分配策略是按将消费者组内所有消费者及消费者订阅的所有主题的分区按照字典排序,然后通过轮询的方式分配给每个消费者。
比如有 3 个消费者 A,B,C,订阅了 3 个 topic ,t0,t1,t2,每个 topic 各有 3 个分区 p0,p1,p2。 如果 A 订阅了 t0,B 订阅了 t0 和 t1,C 订阅了 t0,t1,t2,那么分配的情况如下:
消费者A:t0-p0 消费者B:t1-p0 消费者C:t1-p1,t2-p0,t2-p1,t2-p2
这样也会出现分配不均匀的情况,按照订阅情况来讲完全可以吧 t1p1 分配给消费者B
3.StickyAssignor分配策略
这种分配策略有两个目的
1.分区的分配要尽可能的均匀
2.分区的分配尽可能的与上次分配的保持相同。
当两者发生冲突时,第一个目标优先于第二个目标。
假设消费组内有3个消费者:C0、C1、C2
它们都订阅了4个主题:t0、t1、t2、t3
并且每个主题有2个分区,也就是说整个消费组订阅了,t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1 这10个分区
最终的分配结果如下:
消费者C0:t0p0、t1p1、t3p0 消费者C1:t0p1、t2p0、t3p1 消费者C2:t1p0、t2p1
这样初看上去似乎与采用RoundRobinAssignor策略所分配的结果相同
此时假设消费者C1脱离了消费组,那么消费组就会执行再平衡操作,进而消费分区会重新分配。如果采用RoundRobinAssignor策略,那么此时的分配结果如下:
消费者C0:t0p0、t1p0、t2p0、t3p0 消费者C2:t0p1、t1p1、t2p1、t3p1
如分配结果所示,RoundRobinAssignor策略会按照消费者C0和C2进行重新轮询分配。而如果此时使用的是StickyAssignor策略,那么分配结果为:
消费者C0:t0p0、t1p1、t3p0、t2p0 消费者C2:t1p0、t2p1、t0p1、t3p1
可以看到分配结果中保留了上一次分配中对于消费者C0和C2的所有分配结果,并将原来消费者C1的“负担”分配给了剩余的两个消费者C0和C2,最终C0和C2的分配还保持了均衡。
如果发生分区重分配,那么对于同一个分区而言有可能之前的消费者和新指派的消费者不是同一个,对于之前消费者进行到一半的处理还要在新指派的消费者中再次复现一遍,这显然很浪费系统资源。StickyAssignor策略如同其名称中的“sticky”一样,让分配策略具备一定的“粘性”,尽可能地让前后两次分配相同,进而减少系统资源的损耗以及其它异常情况的发生。
到目前为止所分析的都是消费者的订阅信息都是相同的情况,我们来看一下订阅信息不同的情况下的处理。
举例: 同样消费组内有3个消费者:C0、C1、C2
集群中有3个主题 t0、t1、t2
这3个主题分别有 1、2、3个分区
也就是说集群中有 t0p0、t1p0、t1p1、t2p0、t2p1、t2p2 这6个分区
消费者C0订阅了主题t0,消费者C1订阅了主题t0和t1,消费者C2订阅了主题t0、t1和t2
如果此时采用RoundRobinAssignor策略:
消费者C0:t0p0 消费者C1:t1p0 消费者C2:t1p1、t2p0、t2p1、t2p2
如果此时采用的是StickyAssignor策略:
消费者C0:t0p0 消费者C1:t1p0、t1p1 消费者C2:t2p0、t2p1、t2p2
此时消费者C0脱离了消费组,那么RoundRobinAssignor策略的分配结果为:
消费者C1:t0p0、t1p1 消费者C2:t1p0、t2p0、t2p1、t2p2
StickyAssignor策略,那么分配结果为:
消费者C1:t1p0、t1p1、t0p0 消费者C2:t2p0、t2p1、t2p2
可以看到StickyAssignor策略保留了消费者C1和C2中原有的5个分区的分配:
t1p0、t1p1、t2p0、t2p1、t2p2。
从结果上看StickyAssignor策略比另外两者分配策略而言显得更加的优异,这个策略的代码实现也是异常复杂。
4.自定义分区分配策略
可以通过实现 org.apache.kafka.clients.consumer.internals.PartitionAssignor 接口来实现
18.kafka 控制器是什么?有什么作用
在 Kafka 集群中会有一个或多个 broker,其中有一个 broker 会被选举为控制器,它负责管理整个集群中所有分区和副本的状态,kafka 集群中只能有一个控制器。
当某个分区的 leader 副本出现故障时,由控制器负责为该分区选举新的 leader 副本。
当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有 broker 更新其元数据信息。
当为某个 topic 增加分区数量时,由控制器负责分区的重新分配。
最近建一些几十个工作内推群,各大城市都有,群里目前已经收集了很多内推岗位,大厂、中厂、小厂、外包都有。 欢迎HR、开发、测试、运维和产品加入。

扫描下方微信,备注:网站+所在城市,即可拉你进工作内推群。
