Kafka高频面试题20问
1. 请介绍一下Kafka是什么?
Kafka是一个分布式的基于发布/订阅模式的消息队列,使用Scala编写,主要应用于大数据实时处理领域。它最初由Linkedin公司开发,之后贡献给了Apache基金会并成为顶级开源项目。Kafka能够实时处理大量数据,适用于多种需求场景,如基于Hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,以及Web/Nginx日志、访问日志处理和消息服务等。
2. 请说明Kafka的基础架构
- Producer:消息生产者,负责向Kafka集群发送消息(写入消息)。
- Consumer:消息消费者,从Kafka集群拉取消息并进行消费处理。
- Broker:Kafka的节点,分区(Partition)数据就保存在Broker上面。一个Kafka集群由多个Broker组成。
- Topic:主题,是消息的逻辑分类,生产者将消息发送到特定的Topic,消费者通过订阅Topic来获取消息。
- Partition:为实现扩展性,一个大的Topic可分布到多个Broker上,每个Topic可分为多个Partition,每个Partition是一个有序的队列。通过分区,Kafka可以并行处理消息,提高吞吐量。
- Zookeeper:在Kafka中,Zookeeper用于管理集群成员信息、选举领导者(如Partition的Leader)以及维护Topic和Partition的元数据等,对Kafka集群的正常运行起着关键的协调作用 。
3. Kafka的常用操作指令有哪些?
- 创建Topic:
bin/kafka-topics.sh --create --bootstrap-server <bootstrap-server:port> --replication-factor <num-replicas> --partitions <num-partitions> --topic <topic-name>
例如:
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test-topic
- 查看Topic列表:
bin/kafka-topics.sh --list --bootstrap-server <bootstrap-server:port>
- 删除Topic:(需要注意,Kafka删除Topic功能默认是关闭的,如需开启,要在server.properties中设置delete.topic.enable=true)
bin/kafka-topics.sh --delete --bootstrap-server <bootstrap-server:port> --topic <topic-name>
- 发送消息(生产者):
bin/kafka-console-producer.sh --bootstrap-server <bootstrap-server:port> --topic <topic-name>
输入消息后按回车键即可发送。
- 消费消息(消费者):
bin/kafka-console-consumer.sh --bootstrap-server <bootstrap-server:port> --topic <topic-name> --from-beginning
--from-beginning参数表示从Topic的起始位置开始消费消息。
4. Kafka的文件存储机制是怎样的?
Kafka的消息会不断追加到日志(log)文件末尾。为防止log文件过大影响数据定位效率,采取了分片和索引机制,将每个Partition分为多个Segment。每个Segment对应两个主要文件:“.index”索引文件和“.log”数据文件(在新版本中还有一个.timeindex时间索引文件,依据数据读取时间建立索引 ),这些文件存于以“Topic名称 + 分区序号”命名的文件夹下。例如,名为“example - topic”的Topic若有两个分区,则对应文件夹为“example - topic - 0”、“example - topic - 1” 。
Segment是逻辑概念,实际并不存在。Index文件存储大量索引信息(属于稀疏索引,默认每隔4KB建立一个索引),Log文件存储数据,索引文件中的元数据指向对应数据文件中消息的物理偏移地址。
通过配置index.interval.bytes(索引间隔,默认值4096字节,即4KB)可控制插多少条消息后建立一个索引,这使得Kafka索引为稀疏索引,能避免索引文件占用过多内存,从而在内存中保存更多索引。同时,通过segment.bytes(默认值1073741824字节,即1GB)控制日志段文件大小,当文件存储超过1GB后会新起一个文件写入;也可通过log.segment.ms参数以时间维度进行文件切分。
当指定一个offset查找对应消息时,Kafka的查找过程如下:首先根据offset确定数据在Partition的哪个Segment;接着从Segment的Index文件中找到offset在Log文件中的区间;最后扫描Log文件的该区间找到对应数据。
5. Kafka新建的分区会在哪个目录下创建?
在启动Kafka集群前,需配置log.dirs参数(也可配置log.dir,二者含义相同,设置一个即可),其值为Kafka数据存放目录,可配置多个目录,用逗号分隔,这些目录通常分布在不同磁盘以提升读写性能 。
若log.dirs只配置一个目录,那么分配到各个Broker上的分区只能在此目录下创建文件夹存储数据。
若log.dirs配置了多个目录,Kafka会在含有分区目录最少的文件夹中创建新的分区目录,分区目录名是“Topic名称 + 分区序号”。注意,依据的是分区文件夹总数最少,而非磁盘使用量最少。也就是说,若给log.dirs新增一个磁盘,新分区目录会优先在此新磁盘上创建,直至该新磁盘目录拥有的分区目录数不再是最少。
6. Kafka的分区策略有哪些?
当Producer发送数据时,需封装成ProducerRecord对象,ProducerRecord可指定分区、key和value 。常见的分区策略如下:
- 指定分区策略:若在ProducerRecord中明确指定了分区号(partition),则消息会直接发送到该指定分区。
- 基于Key的分区策略:当未指定分区但设置了Key时,Kafka会对Key进行Hash计算,然后将Hash值与Topic的分区总数取余,所得结果即为消息要发送到的分区号。通过这种方式,能保证具有相同Key的消息始终发送到同一个分区,有助于实现一些需要顺序处理或特定逻辑的数据处理,比如按用户ID进行数据处理时,同一用户的消息会在同一分区。
- 轮询分区策略:若既未指定分区也没有设置Key,Kafka采用轮询(round - robin)策略。首次调用时,会随机生成一个整数,后续每次调用在此整数基础上自增,然后将该值与Topic可用的分区总数取余,得到的结果就是消息发送的分区号 。这种策略可使消息较为均匀地分布在各个分区上,实现负载均衡。
7. Kafka如何保证数据的可靠性?
Kafka从多个方面保证数据可靠性:
- 生产者端:
- ACK机制:Producer有三种ACK机制。
- ACK=0:Producer发送消息后,无需等待Leader的确认响应,就认为消息发送成功并继续发送下一批消息。这种方式具有最低延迟,但可靠性最差,当服务器发生故障时,消息很可能丢失。
- ACK=1:默认设置。Producer发送消息后,需要等待Leader确认已成功接收数据才发送下一批消息,但不等待Follower副本的确认。若Leader宕机且Follower尚未复制该消息,数据就会丢失。此机制提供了较好的持久性和较低的延迟性。
- ACK=-1(all):Producer发送消息后,Leader接收到消息,还必须要求ISR(In - Sync Replicas,副本同步队列)列表里与Leader保持同步的Follower都确认消息已同步,Producer才发送下一批消息。这种机制的持久性可靠性最好,但延时性最差。
- 重试机制:当Producer发送消息失败时,可通过配置重试次数和重试间隔时间,自动重试发送消息,增加消息成功发送的概率。
- ACK机制:Producer有三种ACK机制。
- Broker端:
- 副本机制:每个Topic的分区可配置多个副本(replica),其中一个副本为Leader,其余为Follower。Leader负责处理读写请求,Follower从Leader同步数据。当Leader出现故障时,会从ISR中的Follower中选举出新的Leader,继续提供服务,保证数据的可用性。
- 日志持久化:消息被持久化到本地磁盘,并且支持数据备份,防止数据丢失。通过配置刷盘策略(如同步刷盘或异步刷盘)来确保消息真正写入磁盘。同步刷盘时,Broker会等待消息写入磁盘后才向Producer返回确认响应,数据安全性高,但可能影响性能;异步刷盘则是将消息先写入内存缓冲区,由后台线程异步将缓冲区数据写入磁盘,性能较高,但在系统故障时可能丢失少量未刷盘的数据。
- 消费者端:
- 偏移量(Offset)管理:消费者通过维护偏移量来记录已消费消息的位置。消费者可以自动提交偏移量,也可手动提交。自动提交时,消费者定期将已消费消息的偏移量提交给Kafka;手动提交则由应用程序在合适的时机(如消息成功处理后)提交偏移量。如果消费者在处理消息过程中失败,重新启动后可根据偏移量继续从上次消费的位置读取消息,避免消息丢失或重复消费。
8. Kafka的Exactly Once语义是如何实现的?
在0.11.0.0版本之前,Kafka只能保证At Least Once(至少一次)或At Most Once(至多一次)语义。从0.11.0.0版本开始引入了幂等性和事务机制来实现Exactly Once语义 。
- 幂等性:
- Producer开启幂等性只需将
enable.idempotence参数设置为true。开启幂等性后,Producer会为每个发送的消息分配一个唯一的PID(Producer ID)和Sequence Number(序列号)。 - 当Producer发送消息时,Broker会缓存每个分区最后一条消息的PID和Sequence Number。若Broker接收到的消息的PID和Sequence Number与缓存中的不一致,且新的Sequence Number大于缓存中的Sequence Number,则认为是新消息,将其写入;若Sequence Number小于缓存中的值,则丢弃该消息;若PID和Sequence Number都与缓存中的一致,则说明该消息已被处理过,直接丢弃。通过这种方式,在单个Producer对单个分区发送消息时,能保证消息不会重复写入。
- Producer开启幂等性只需将
- 事务:
- 事务机制允许Producer在一个事务中发送多条消息到多个分区,并且确保这些消息要么全部成功写入,要么全部失败回滚。Producer通过
initTransactions方法初始化事务,然后使用beginTransaction开始事务,在事务中发送消息,最后使用commitTransaction提交事务或abortTransaction回滚事务。 - Kafka通过引入Transaction Coordinator(事务协调器)来管理事务。Transaction Coordinator为每个Producer分配一个唯一的Transaction ID,该ID与Producer的PID相关联。在事务提交过程中,Transaction Coordinator会协调各个Broker,确保所有涉及的分区都成功提交或回滚事务相关的消息。
- 消费者端也需要配合实现Exactly Once语义。消费者通过
isolation.level参数来设置隔离级别,当设置为“read_committed”时,消费者只会读取已成功提交事务的消息,避免读取到未提交或部分提交的消息,从而在消费端保证了消息的一致性和Exactly Once语义。
- 事务机制允许Producer在一个事务中发送多条消息到多个分区,并且确保这些消息要么全部成功写入,要么全部失败回滚。Producer通过
9. 消息是采用Pull模式,还是Push模式?
Kafka采用Pull(拉取)模式,即Consumer主动从Broker拉取消息 。最初在设计时考虑过Push(推送)模式,即Broker将消息推送给Consumer,但最终选择了Pull模式,原因如下:
- 消费速率控制:在Push模式下,由Broker决定消息推送的速率,难以适应不同消费速率的Consumer。当Broker推送速率远大于Consumer消费速率时,Consumer可能因处理不过来而崩溃。而Pull模式下,Consumer可以自主决定拉取消息的速率和批量大小,根据自身的消费能力来拉取数据,更好地控制消费过程。
- 批量拉取:Pull模式允许Consumer根据自身需求决定是否批量从Broker拉取数据。而Push模式必须在不了解Consumer消费能力和策略的情况下,决定是立即推送每条消息还是缓存后批量推送。若采用较低推送速率避免Consumer崩溃,可能导致每次推送消息量较少,造成资源浪费。
- 避免空轮询:Pull模式下,如果Broker没有可供消费的消息,Consumer不断轮询会消耗资源。Kafka通过设置参数,可让Consumer阻塞等待,直到有新消息到达(也可设置阻塞到消息数量达到特定量后批量拉取),避免了无效的轮询。
10. Kafka的分区分配策略有哪些?
Kafka的分区分配策略用于决定Consumer Group中的各个Consumer如何分配Topic的分区,常见的分配策略有以下几种:
- RangeAssignor策略:这是默认的分区分配策略。它先对每个Topic的分区按序号排序,再对Consumer Group中的Consumer按名称排序。然后将分区范围平均分配给每个Consumer。例如,有2个Consumer(C0、C1),3个分区(P0、P1、P2),对于每个Topic,分区分配情况可能是C0分配到P0、P1,C1分配到P2。这种策略在分区数不能被Consumer数量整除时,可能导致部分Consumer分配到过多分区,负载不均衡。
- RoundRobinAssignor策略:该策略将所有Topic的分区及所有Consumer统一进行轮询分配。先将所有Topic的分区按顺序排列,再将Consumer按顺序排列,然后依次将分区分配给Consumer。例如,有2个Consumer(C0、C1),2个Topic(T0有P0、P1分区,T1有P2、P3分区),分配结果可能是C0分配到P0、P2,C1分配到P1、P3。这种策略能更均匀地分配分区,但可能导致一个Consumer消费多个Topic的分区,增加了管理复杂度。
- StickyAssignor策略:StickyAssignor策略在保证分区分配尽可能均匀的同时,尽量维持已有的分配方案。当发生再均衡(如Consumer加入或离开组)时,它会尝试最小化分区的重新分配。例如,已有分配方案中C0消费P0、P1,C1消费P2、P3,若新增一个Consumer C2,StickyAssignor策略可能会尽量保持C0、C1的大部分分区不变,将少量分区调整给C2,减少因再均衡导致的消费中断和数据重复消费等问题。
11. Kafka的哪些设计让它有如此高的性能?
- 顺序读写:Kafka的消息存储采用日志文件形式,消息不断追加到日志文件末尾,属于顺序写操作。磁盘顺序写性能远高于随机写,大大提高了消息写入效率。在读取消息时,通过索引文件可快速定位到消息在日志文件中的位置,实现顺序读,也提升了读取性能。
- 零拷贝技术:Kafka在数据传输过程中采用零拷贝技术,减少数据在用户空间和内核空间之间的拷贝次数。传统的数据传输需要从磁盘读取数据到内核缓冲区,再拷贝到用户缓冲区,然后通过网络发送;而零拷贝技术直接在内核空间将数据从磁盘缓冲区传输到网络缓冲区,避免了不必要的拷贝操作,提高了数据传输效率,降低了CPU和内存的开销。
- 批量处理:Producer可以将多条消息批量发送到Broker,减少网络请求次数,降低网络开销。Consumer也可以批量拉取消息,提高数据处理效率。同时,Kafka的内存缓冲区会缓存一定量的数据后再进行写入或发送操作,进一步提升了整体性能。
- 分区机制:通过将Topic划分为多个分区,Kafka实现了并行处理。不同分区可以分布在不同的Broker上,生产者和消费者可以并行地对不同分区进行读写操作,从而提高了系统的吞吐量和并发处理能力。
- 缓存机制:Kafka使用了页缓存(Page Cache)技术,操作系统会将磁盘中的数据缓存到内存的页缓存中。当Kafka进行读写操作时,首先会在页缓存中查找数据,若命中则直接读取,减少了磁盘I/O操作。对于写操作,数据先写入页缓存,由操作系统异步将数据刷盘,提高了写入性能。
12. Zookeeper在Kafka中的作用是什么?
- 集群管理:Zookeeper用于管理Kafka集群成员信息。Kafka集群中的每个Broker启动时,会在Zookeeper上创建一个临时节点(Ephemeral Node),Zookeeper通过心跳机制监控这些节点状态。若某个Broker与Zookeeper的会话超时,对应的临时节点会被删除,其他Broker可以感知到该Broker的故障,从而进行相应的处理,如重新选举分区的Leader等 。
- 领导者选举:在Kafka中,分区的Leader选举依赖于Zookeeper。当一个分区的Leader出现故障时,Kafka会借助Zookeeper来选举新的Leader。每个Follower会在Zookeeper上创建一个用于选举的临时顺序节点,通过比较节点顺序来确定谁成为新的Leader。
- 元数据管理:Zookeeper存储了Kafka的Topic、Partition等元数据信息。Producer和Consumer在与Kafka交互时,需要从Zookeeper获取这些元数据,如Topic的分区数量、每个分区的Leader所在Broker等,以便进行消息的发送和消费。
- 负载均衡:Zookeeper协助Kafka实现负载均衡。通过维护Broker和分区的状态信息,Kafka可以根据这些信息将读写请求合理地分配到各个Broker和分区上,保证集群资源的有效利用和系统性能的稳定。
13. 当使用kafka - topics.sh创建了一个topic之后,Kafka背后会执行什么逻辑?
- 参数解析:首先,kafka - topics.sh脚本会解析用户输入的创建Topic的参数,包括Topic名称、分区数、副本因子等。
- 与Zookeeper通信:脚本通过Zookeeper客户端连接到Zookeeper集群,在Zookeeper的
/brokers/topics节点下创建一个以以该Topic名称命名的节点,节点数据包含该Topic的分区数、副本因子等配置信息。例如,若创建一个名为“test - topic”,包含3个分区、副本因子为2的Topic,会在/brokers/topics下创建/brokers/topics/test - topic节点,节点数据可能类似{"partitions":3,"replication - factor":2}。 - 分配分区:Kafka会根据配置的分区数,为每个分区在Zookeeper上创建对应的节点。这些节点位于
/brokers/topics/<topic - name>/partitions路径下,每个分区节点名称为分区编号。例如,对于上述“test - topic”,会创建/brokers/topics/test - topic/partitions/0、/brokers/topics/test - topic/partitions/1、/brokers/topics/test - topic/partitions/2等节点 。 - 副本分配:Kafka会根据副本因子和Broker的负载情况,为每个分区分配副本,并将副本分配信息记录在Zookeeper相应分区节点的数据中。假设集群中有3个Broker(Broker0、Broker1、Broker2),对于“test - topic”的分区0,可能的副本分配记录为
{"leader":0,"replicas":[0,1]},表示分区0的Leader副本在Broker0上,Follower副本在Broker0和Broker1上 。 - 通知相关组件:创建完成后,Zookeeper会通过Watcher机制通知Kafka集群中的其他组件(如Producer、Consumer和Broker),新Topic已创建及相关元数据信息已更新。Producer和Consumer在后续与Kafka交互时,会从Zookeeper获取最新的Topic元数据,以正确地发送和消费消息 。
14. Kafka消费者组(Consumer Group)的工作原理是什么?
Kafka消费者组是一组消费者实例的集合,它们共同消费一个或多个Topic的消息。每个消费者组有一个唯一的Group ID 。
- 消费者组注册:当消费者启动时,会向Kafka集群注册自己所属的消费者组。Kafka会在内部维护消费者组的相关信息,如消费者组的成员列表、消费者组订阅的Topic等 。
- 分区分配:消费者组内的消费者通过Kafka的分区分配策略来分配Topic的分区。常见的分配策略有RangeAssignor、RoundRobinAssignor和StickyAssignor 。
- RangeAssignor:先对每个Topic的分区按序号排序,再对消费者组中的消费者按名称排序。然后将分区范围平均分配给每个消费者。例如,有2个消费者(C0、C1),3个分区(P0、P1、P2),对于每个Topic,分区分配情况可能是C0分配到P0、P1,C1分配到P2。这种策略在分区数不能被消费者数量整除时,可能导致部分消费者分配到过多分区,负载不均衡 。
- RoundRobinAssignor:将所有Topic的分区及所有消费者统一进行轮询分配。先将所有Topic的分区按顺序排列,再将消费者按顺序排列,然后依次将分区分配给消费者。例如,有2个消费者(C0、C1),2个Topic(T0有P0、P1分区,T1有P2、P3分区),分配结果可能是C0分配到P0、P2,C1分配到P1、P3。这种策略能更均匀地分配分区,但可能导致一个消费者消费多个Topic的分区,增加了管理复杂度 。
- StickyAssignor:在保证分区分配尽可能均匀的同时,尽量维持已有的分配方案。当发生再均衡(如消费者加入或离开组)时,它会尝试最小化分区的重新分配。例如,已有分配方案中C0消费P0、P1,C1消费P2、P3,若新增一个Consumer C2,StickyAssignor策略可能会尽量保持C0、C1的大部分分区不变,将少量分区调整给C2,减少因再均衡导致的消费中断和数据重复消费等问题 。
- 消息消费:分区分配完成后,每个消费者负责消费分配给自己的分区中的消息。消费者从分区的起始位置(或上次消费的偏移量位置)开始拉取消息,并进行处理。消费者通过向Kafka集群发送心跳来保持自己在消费者组中的活跃状态 。
- 偏移量管理:消费者在消费消息时,会维护已消费消息的偏移量(Offset)。偏移量表示消费者在分区中消费到的位置。在早期版本中,偏移量存储在Zookeeper中,从Kafka 0.9版本开始,偏移量默认存储在Kafka内部的
__consumer_offsetsTopic中。消费者可以自动提交偏移量(定期将已消费消息的偏移量提交给Kafka),也可以手动提交偏移量(由应用程序在合适的时机,如消息成功处理后,提交偏移量) 。 - 再均衡(Rebalance):当消费者组内的消费者成员发生变化(如新增消费者、消费者离开或故障),或者消费者组订阅的Topic发生变化(如Topic新增分区)时,会触发再均衡。再均衡过程中,Kafka会重新分配分区给消费者组内的消费者,以确保消息的负载均衡和正确消费。在再均衡期间,消费者组的消费会短暂中断 。
15. Kafka如何进行故障恢复?
- Broker故障恢复:
- 副本切换:每个Topic的分区有多个副本,其中一个为Leader副本,其余为Follower副本。当Leader Broker发生故障时,Zookeeper会感知到该Broker的临时节点消失,触发分区的Leader选举。从与Leader保持同步的Follower副本(即处于ISR列表中的副本)中选举出新的Leader。新的Leader会继续提供读写服务,消费者和生产者可以继续与新Leader所在的Broker进行交互,从而保证数据的可用性 。
- 数据恢复:新的Leader选举出来后,需要确保数据的一致性。对于已经被确认的消息,即使旧Leader在故障前还未将部分消息完全同步到Follower,由于这些消息已经被生产者确认,新Leader会从自身已有的数据中恢复这些消息。对于未被确认的消息,生产者会根据ACK机制进行重试发送,以确保消息不丢失 。
- 消费者故障恢复:
- 偏移量恢复:消费者通过维护偏移量来记录已消费消息的位置。当消费者发生故障重启后,会从之前记录的偏移量位置继续消费消息。如果是自动提交偏移量,消费者会从最近一次提交的偏移量处开始消费;如果是手动提交偏移量,应用程序需要确保在消息成功处理后才提交偏移量,这样消费者重启后能从正确的位置恢复消费,避免消息重复消费或丢失 。
- 重新加入消费者组:故障恢复后的消费者会重新向Kafka集群注册,加入到原来的消费者组中。如果消费者组在该消费者故障期间发生了再均衡,那么分区分配可能已经发生变化,消费者会根据新的分区分配情况继续消费消息 。
- 生产者故障恢复:
- 重试机制:生产者在发送消息时,如果遇到网络故障、Broker不可用等问题导致消息发送失败,会根据配置的重试次数和重试间隔时间进行重试。例如,若设置重试次数为3,当第一次发送失败后,生产者会等待一定时间(如1秒)后重试,最多重试3次,增加消息成功发送的概率 。
- 幂等性与事务:从Kafka 0.11.0.0版本开始,生产者支持幂等性和事务机制。开启幂等性后,生产者发送的消息会带有唯一的PID和Sequence Number,Broker会根据这些信息避免重复写入相同消息。事务机制允许生产者在一个事务中发送多条消息到多个分区,确保这些消息要么全部成功写入,要么全部失败回滚,保证了生产者在故障恢复过程中数据的一致性 。
16. Kafka的消费模式有哪些,有什么区别?
Kafka有两种消费模式:
- 点对点(Point - to - Point)消费模式:
- 在这种模式下,每个消息只能被一个消费者消费。一个Topic可以被多个消费者组订阅,但每个消费者组内只有一个消费者能消费到消息。例如,有消费者组C1(包含消费者C1 - 1、C1 - 2)和消费者组C2(包含消费者C2 - 1、C2 - 2)订阅了Topic “T1”。对于C1组,消息只会被C1 - 1或C1 - 2中的一个消费者消费;对于C2组,消息只会被C2 - 1或C2 - 2中的一个消费者消费 。
- 这种模式适用于每个消息只需要被处理一次的场景,比如订单处理系统,每个订单消息只需要一个消费者进行处理 。
- 发布/订阅(Publish/Subscribe)消费模式:
- 该模式下,一个消息可以被多个消费者消费。所有订阅了某个Topic的消费者都能接收到该Topic的消息。例如,有多个消费者(C1、C2、C3……)都订阅了Topic “T2”,那么生产者发送到“T2”的每一条消息,C1、C2、C3等消费者都可以消费到 。
- 这种模式适用于需要将消息广播给多个消费者进行不同处理的场景,比如实时监控系统,不同的监控组件需要从同一个消息源获取数据进行各自的分析和处理 。
- 实际上,Kafka的消费者组机制可以模拟这两种模式。当每个消费者组只有一个消费者时,就类似于点对点消费模式;当多个消费者组订阅同一个Topic,且每个消费者组内有多个消费者时,就类似于发布/订阅消费模式 。
17. Kafka中什么是ISR、AR?
- ISR(In - Sync Replicas):即副本同步队列,是指与Leader副本保持同步的Follower副本集合 。
- 当生产者发送消息到Kafka集群时,消息首先被写入Leader副本。只有当ISR中的Follower副本也成功同步了该消息,生产者才会收到来自Broker的确认响应(取决于ACK机制)。例如,若ACK设置为-1(all),则只有当所有ISR中的Follower副本都同步了消息,生产者才认为消息发送成功 。
- ISR中的副本是实时与Leader副本保持同步的,它们的日志与Leader副本的日志差距在可接受范围内。若某个Follower副本长时间未从Leader副本同步数据,导致其与Leader副本的差距超过一定阈值,该Follower副本会被从ISR中移除。当该Follower副本重新追上Leader副本时,又会被重新加入ISR 。
- ISR在Kafka的高可用性和数据一致性方面起着关键作用。在Leader副本发生故障时,新的Leader会从ISR中的Follower副本中选举产生,以确保新的Leader拥有最新的数据 。
- AR(Assigned Replicas):即分配副本,是指某个分区的所有副本集合,包括Leader副本和Follower副本 。
- 例如,对于一个设置了副本因子为3的分区,AR可能包含三个副本,其中一个为Leader副本,另外两个为Follower副本。AR中的副本分布在不同的Broker上,以实现数据的冗余存储和高可用性 。
- AR中的副本并不一定都在ISR中。某些副本可能由于网络延迟、硬件故障等原因,与Leader副本不同步,从而不在ISR中。但它们仍然属于AR的一部分,在故障恢复或重新同步后,有可能重新加入ISR 。
18. Kafka的吞吐量为什么这么高?
- 顺序读写:
- Kafka的消息存储采用日志文件形式,消息不断追加到日志文件末尾,属于顺序写操作。磁盘顺序写性能远高于随机写,因为随机写时磁盘磁头需要频繁移动到不同位置,而顺序写时磁头只需按顺序移动,减少了寻道时间和旋转延迟,大大提高了消息写入效率 。
- 在读取消息时,通过索引文件可快速定位到消息在日志文件中的位置,实现顺序读。例如,通过Offset索引可以快速找到对应的消息块,然后顺序读取消息,也提升了读取性能 。
- 零拷贝技术:
- Kafka在数据传输过程中采用零拷贝技术,减少数据在用户空间和内核空间之间的拷贝次数。传统的数据传输需要从磁盘读取数据到内核缓冲区,再拷贝到用户缓冲区,然后通过网络发送;而零拷贝技术直接在内核空间将数据从磁盘缓冲区传输到网络缓冲区,避免了不必要的拷贝操作 。
- 例如,在Linux系统中,通过
sendfile系统调用实现零拷贝。数据从磁盘文件读取到内核缓冲区后,直接在内核空间将数据传输到Socket缓冲区,无需经过用户空间,提高了数据传输效率,降低了CPU和内存的开销 。
- 批量处理:
- Producer可以将多条消息批量发送到Broker,减少网络请求次数,降低网络开销。例如,Producer可以将一批消息(如100条)打包成一个请求发送给Broker,而不是一条消息发送一次请求,这样大大减少了网络传输的时间和资源消耗 。
- Consumer也可以批量拉取消息,提高数据处理效率。Consumer可以一次拉取一定数量的消息(如500条),然后批量进行处理,减少了拉取消息的次数,提高了整体的消费效率。同时,Kafka的内存缓冲区会缓存一定量的数据后再进行写入或发送操作,进一步提升了整体性能 。
- 分区机制:
- 通过将Topic划分为多个分区,Kafka实现了并行处理。不同分区可以分布在不同的Broker上,生产者和消费者可以并行地对不同分区进行读写操作。例如,一个Topic有10个分区,分布在5个Broker上,生产者可以同时向这10个分区发送消息,消费者也可以同时从这10个分区拉取消息,从而提高了系统的吞吐量和并发处理能力 。
- 分区机制还使得Kafka可以通过增加分区数量来横向扩展系统性能,当系统负载增加时,可以通过增加分区和Broker数量来处理更多的消息 。
- 缓存机制:
- Kafka使用了页缓存(Page Cache)技术,操作系统会将磁盘中的数据缓存到内存的页缓存中。当Kafka进行读写操作时,首先会在页缓存中查找数据,若命中则直接读取,减少了磁盘I/O操作 。
- 对于写操作,数据先写入页缓存,由操作系统异步将数据刷盘,提高了写入性能。例如,Producer发送的消息先写入页缓存,然后由操作系统在合适的时机将页缓存中的数据持久化到磁盘,这样Producer无需等待数据真正写入磁盘,就可以继续发送下一批消息,提高了写入速度 。
19. Kafka与其他消息队列(如RabbitMQ、RocketMQ)相比,有什么特点?
- 性能方面:
- 高吞吐量:Kafka的设计专注于处理大规模的消息流,其顺序读写、零拷贝、批量处理、分区并行处理以及缓存机制等,使其在高并发场景下具有极高的吞吐量。例如,在大数据日志收集、实时流处理等场景中,Kafka能够每秒处理数万甚至数十万条消息 。
- 低延迟:通过优化的网络架构和数据存储结构,Kafka在保证高吞吐量的同时,也能提供较低的延迟,尤其是在消息批量处理和异步操作的情况下,消息的传输和处理延迟可以控制在毫秒级 。相比之下,RabbitMQ在吞吐量和低延迟方面表现相对较弱,适合处理一些对可靠性要求极高、消息量相对较小、延迟敏感的场景 ;RocketMQ在吞吐量和延迟方面也有不错的表现,但与Kafka相比,在大规模数据处理场景下,Kafka的性能优势可能更明显 。
- 可靠性方面:
- 多副本机制:Kafka通过多副本机制保证数据的可靠性,每个分区可以配置多个副本,其中一个为Leader副本,其余为Follower副本。Leader副本负责处理读写请求,Follower副本从Leader副本同步数据。当Leader副本出现故障时,会从ISR中的Follower副本中选举出新的Leader,继续提供服务,确保数据不丢失 。
- ACK机制:Producer可以通过配置ACK机制来控制消息发送的可靠性。ACK = 0时,Producer无需等待Broker确认就认为消息发送成功,延迟最低但可靠性最差;ACK = 1时,Producer等待Leader确认消息已接收;ACK = -1(all)时,Producer等待所有ISR中的副本都确认消息已同步才认为发送成功,可靠性最高但延迟也最高 。RabbitMQ通过持久化队列、事务机制等保证消息可靠性,但在高并发场景下,其性能可能会受到影响 ;RocketMQ同样具备高可靠性,通过同步/异步刷盘和多副本机制保证消息不丢失,与Kafka在可靠性方面各有优势,但在大规模集群部署和复杂业务场景下,Kafka的可靠性机制经过了更多实践的检验 。
- 功能特性方面:
- 丰富的消息模型:Kafka支持简单的发布/订阅模型和消费者组模型,消费者组模型可以实现消息的负载均衡和重复消费控制,适用于多种业务场景 。
- 实时流处理支持:Kafka本身就具备一定的实时流处理能力,结合Kafka Streams等工具,可以方便地进行实时数据处理和分析 。RabbitMQ功能侧重于传统的消息队列应用,如任务队列、异步通信等,在流处理方面功能较弱 ;RocketMQ除了基本的消息队列功能外,还支持顺序消息、事务消息等高级特性,在一些对消息顺序和事务性要求严格的业务场景中有独特优势,而Kafka在这些方面的功能相对较弱,但在实时流处理的生态和工具支持方面更丰富 。