Apache Kafka最早是由LinkedIn开源出来的分布式消息系统,现在是Apache旗下的一个顶级子项目,并且已经成为开源领域应用最广泛的消息系统之一。在大数据的面试中,对Kafka的考察也是必不可少的,本文就将面试中常问到的问题进行了总结并给出了参考答案。


01

简述Kafka的主要架构


生产者、Broker、消费者、ZK;

注意:Zookeeper中保存Broker id和消费者offsets等信息,但是没有生产者信息。


Kafka的主要架构如图所示

Kafka面试题,有这一篇就够了!_java


Kafka的zookeeper中存储结构如图所示

Kafka面试题,有这一篇就够了!_java_02


02

如何配置Kafka的机器数据量、副本数和日志保存时间


Kafka机器数量 = 2 *(峰值生产速度 * 副本数 / 100)+ 1

Kafka机器数量(经验公式)=2*(峰值生产速度*副本数/100)+1

先拿到峰值生产速度,再根据设定的副本数,就能预估出需要部署Kafka的数量。

比如我们的峰值生产速度是50M/s。副本数为2。

Kafka机器数量=2*(50*2/100)+ 1=3台


数据副本数一般我们设置成2个或3个,很多企业设置为2个。

副本的优势:提高可靠性;副本劣势:增加了网络IO传输。

日志默认保存7天;生产环境建议3天。


03

Kafka如何进行压力测试


Kafka官方自带压力测试脚本(kafka-consumer-perf-test.sh、kafka-producer-perf-test.sh)。Kafka压测时,可以查看到哪个地方出现了瓶颈(CPU,内存,网络IO)。一般都是网络IO达到瓶颈。


04

Kafka中数据量计算及硬盘大小确定


每天总数据量100g,每天产生1亿条日志, 10000万/24/60/60=1150条/每秒钟

平均每秒钟:1150条

低谷每秒钟:50条

高峰每秒钟:1150条*(2-20倍)=2300条-23000条

每条日志大小:0.5k-2k(取1k)

每秒多少数据量:2.0M - 20MB

每天的数据量100g * 2个副本 * 3天 / 70%


04

如何对Kafka实施监控


公司自己开发的监控器;

或者使用开源的监控器:KafkaManager、KafkaMonitor、KafkaEagle。


06

Kafka的ISR副本同步队列


ISR(In-Sync Replicas),副本同步队列。ISR中包括Leader和Follower。如果Leader进程挂掉,会在ISR队列中选择一个服务作为新的Leader。有replica.lag.max.messages(延迟条数)和replica.lag.time.max.ms(延迟时间)两个参数决定一台服务是否可以加入ISR副本队列,在0.10版本移除了replica.lag.max.messages参数,防止服务频繁的进去队列。

任意一个维度超过阈值都会把Follower剔除出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的Follower也会先存放在OSR中。


07

Kafka的分区分配策略


在 Kafka内部存在三种默认的分区分配策略:Range、RoundRobin和Sticky。

Range是默认策略。Range是对每个Topic而言的(即一个Topic一个Topic分),首先对同一个Topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。然后用Partitions分区的个数除以消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。

例如:我们有10个分区,两个消费者(C1,C2),3个消费者线程,10 / 3 = 3而且除不尽。

C1-0 将消费 0, 1, 2, 3 分区

C2-0 将消费 4, 5, 6 分区

C2-1 将消费 7, 8, 9 分区


RoundRobin策略:将所有主题分区组成TopicAndPartition列表,然后对TopicAndPartition列表按照hashCode进行排序,最后按照轮询的方式发给每一个消费线程。

如果同一个消费组内所有的消费者的订阅信息都是相同的,那么 RoundRobinAssignor 策略的分区分配会是均匀的。

举例,假设消费组中有 2 个消费者 c0 和 c1,都订阅了主题 t0 和 t1,并且每个主题都有 3 个分区,那么所订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终的分配结果为:

1
2
消费者c0:t0p0、t0p2、t1p1
消费者c1:t0p1、t1p0、t1p2

如果同一个消费组内的消费者所订阅的信息是不相同的,那么在执行分区分配的时候就不是完全的轮询分配,有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个 Topic,那么在分配分区的时候此消费者将分配不到这个 Topic 的任何分区。

举例,假设消费组内有 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,那么最终的分配结果为:

1
2
3
消费者c0:t0p0
消费者c1:t1p0
消费者c2:t1p1、t2p0、t2p1、t2p2

可以看到 RoundRobinAssignor 策略也不是十分完美,这样分配其实并不是最优解,因为完全可以将分区 t1p1 分配给消费者 c1。


“Sticky”这个单词可以翻译为“粘性的”,Kafka从0.11.x版本开始引入这种分配策略,它主要有两个目的:

  • 分区的分配要尽可能的均匀。

  • 分区的分配尽可能的与上次分配的保持相同。

当两者发生冲突时,第一个目标优先于第二个目标。鉴于这两个目标,StickyAssignor 策略的具体实现要比 RangeAssignor 和 RoundRobinAssignor 这两种分配策略要复杂很多。

我们举例来看一下 StickyAssignor 策略的实际效果。
假设消费组内有 3 个消费者:c0、c1 和 c2,它们都订阅了4个主题:t0、t1、t2、t3,并且每个主题有 2 个分区,也就是说整个消费组订阅了 t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1 这 8 个分区。最终的分配结果如下:

1
2
3
消费者c0:t0p0、t1p1、t3p0
消费者c1:t0p1、t2p0、t3p1
消费者c2:t1p0、t2p1

这样初看上去似乎与采用 RoundRobinAssignor 策略所分配的结果相同,但事实是否真的如此呢?再假设此时消费者 c1 脱离了消费组,那么消费组就会执行再平衡操作,进而消费分区会重新分配。如果采用 RoundRobinAssignor 策略,那么此时的分配结果如下:

1
2
消费者c0:t0p0、t1p0、t2p0、t3p0
消费者c2:t0p1、t1p1、t2p1、t3p1

如分配结果所示,RoundRobinAssignor 策略会按照消费者 c0和 c2 进行重新轮询分配。

而如果此时使用的是 StickyAssignor 策略,那么分配结果为:

1
2
消费者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 策略,那么最终的分配结果如下所示(和讲述RoundRobinAssignor策略时的一样,这样不妨赘述一下):

1
2
3
消费者c0:t0p0
消费者c1:t1p0
消费者c2:t1p1、t2p0、t2p1、t2p2

如果此时采用的是 StickyAssignor 策略,那么最终的分配结果为:

1
2
3
消费者C0:t0p0
消费者C1:t1p0、t1p1
消费者C2:t2p0、t2p1、t2p2

可以看到这是一个最优解(消费者 c0 没有订阅主题 t1 和t2,所以不能分配主题 t1 和 t2 中的任何分区给它,对于消费者 c1 也可同理推断)。

假如此时消费者c0脱离了消费组,那么RoundRobinAssignor策略的分配结果为:

1
2
消费者c1:t0p0、t1p1
消费者c2:t1p0、t2p0、t2p1、t2p2

可以看到 RoundRobinAssignor 策略保留了消费者 c1 和 c2 中原有的 3 个分区的分配:t2p0、t2p1 和 t2p2(针对结果集1)。

而如果采用的是StickyAssignor策略,那么分配结果为:

1
2
消费者C1:t1p0、t1p1、t0p0
消费者C2:t2p0、t2p1、t2p2

可以看到 StickyAssignor 策略保留了消费者 c1 和 c2 中原有的 5 个分区的分配:t1p0、t1p1、t2p0、t2p1、t2p2。
从结果上看 StickyAssignor 策略比另外两者分配策略而言显得更加的优异,这个策略的代码实现也是异常复杂。


08

Kafka会不会丢数据?


Ack = 0,相当于异步发送,消息发送完毕即offset增加,继续生产。

Ack = 1,leader收到leader replica 对一个消息的接受ack才增加offset,然后继续生产。

Ack = -1,leader收到所有replica 对一个消息的接受ack才增加offset,然后继续生产。


09

Kafka消息数据积压,Kafka消费能力不足怎么处理?


1)如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。(两者缺一不可)

2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。


10

Kafka参数优化都有哪些手段?


1)Broker参数配置(server.properties)


1、日志保留策略配置# 保留三天,也可以更短 (log.cleaner.delete.retention.ms)log.retention.hours=72

 

2、Replica相关配置default.replication.factor:1 默认副本1个


3、网络通信延时replica.socket.timeout.ms:30000 #当集群之间网络不稳定时,调大该参数replica.lag.time.max.ms= 600000# 如果网络不好,或者kafka集群压力较大,会出现副本丢失,然后会频繁复制副本,导致集群压力更大,此时可以调大该参数


2)Producer优化(producer.properties)


compression.type:none                gzip  snappy  lz4 #默认发送不进行压缩,推荐配置一种适合的压缩算法,可以大幅度的减缓网络压力和Broker的存储压力。


3)Kafka内存调整(kafka-server-start.sh)

默认内存1个G,生产环境尽量不要超过6个G。

export KAFKA_HEAP_OPTS="-Xms4g -Xmx4g"


11

Kafka如何实现高效读写数据?


1)Kafka本身是分布式集群,同时采用分区技术,并发度高。

2)顺序写磁盘

Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到600M/s,而随机写只有100K/s。

3)零复制技术

Kafka面试题,有这一篇就够了!_java_03

                         


12

Kafka单条日志传输大小


Kafka对于消息体的大小默认为单条最大值是1M但是在我们应用场景中,常常会出现一条消息大于1M,如果不对Kafka进行配置。则会出现生产者无法将消息推送到Kafka或消费者无法去消费Kafka里面的数据,这时我们就要对Kafka进行以下配置:server.properties


replica.fetch.max.bytes:1048576  broker可复制的消息的最大字节数, 默认为1Mmessage.max.bytes:1000012   kafka 会接收单个消息size的最大限制, 默认为1M左右


注意:message.max.bytes必须小于等于replica.fetch.max.bytes,否则就会导致replica之间数据同步失败。


13

Kafka中过期的数据如何处理?


保证数据没有被引用(没人消费他)

日志清理保存的策略只有delete和compact两种

log.cleanup.policy = delete启用删除策略

log.cleanup.policy = compact启用压缩策略

https://www.jianshu.com/p/fa6adeae8eb5


14

Kafka中的数据是有序的吗?



单分区内有序;多分区,分区与分区间无序;

扩展:

kafka producer发送消息的时候,可以指定key:


Kafka面试题,有这一篇就够了!_java_04

                           

这个key的作用是为消息选择存储分区,key可以为空,当指定key且不为空的时候,Kafka是根据key的hash值与分区数取模来决定数据存储到那个分区。

Kafka面试题,有这一篇就够了!_java_05

有序解决方案:同一张表的数据 放到同一个 分区

 => ProducerRecord里传入key,会根据key取hash算出分区号

 => key使用表名,如果有库名,拼接上库名