为心爱的人努力是一种幸福
前言
Kafka 的基础知识点还是蛮多的,本文针对Kafka的一些面试过程中常见的基础知识进行全面总结,方便大家进行查漏补缺。
正文
一. Broker概念
在Kafka 中,一个Kafka 服务端的实例,就叫做一个Broker 。已知Kafka 使用Zookeeper 来维护Kafka集群信息,如下图所示。
Broker 启动时,会在Zookeeper 的 /brokers/ids 路径上创建临时节点,将自己的id 注册到Zookeeper。
Kafka 组件会订阅Zookeeper 的 /brokers/ids 路径,当有Broker 加入或者退出集群时,这些Kafka组件就能够获得通知。
二. Topic概念
可以在Broker 上创建Topic ,生产者向Topic 发送消息,消费者订阅Topic 并从Topic拉取消息。可以用下图进行示意。
三. Partition概念
一个Topic 可以有多个分区,这里的分区就叫做Partition ,分区的作用是提高Kafka的吞吐量。
分区可以用下图进行示意。
可以用下面的指令在创建Topic的时候指定分区,指令如下所示。
shell
./kafka-topics.sh --bootstrap-server 127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094 --replication-factor 3 --partitions 3 --create --topic mytopic-0
上述指令会创建一个分区数为3 ,副本数为3 的Topic。
一个分区可以有多个副本,且一个分区的多个副本不能在同一个Broker 上,例如只有3 个Broker ,但是创建Topic 的时候,指定副本数为4 ,此时创建Topic会失败。
分区的多个副本可以分为leader 节点和follower 节点,且leader 节点为客户端提供读写 功能,follower 节点会从leader节点同步数据但不提供读写功能,这样能避免出现读写不一致的问题。
创建Topic 时,会在Broker 上为这个Topic的分区创建目录,如下所示。
分区目录的内容如下所示。
每个文件含义如下。
- index 文件是索引文件。记录消息的编号;
- timeindex 文件是时间戳索引。记录生产者发送消息的时间和消息记录到日志文件的时间;
- log 是日志文件。记录消息。
因为log 文件会越来越大,此时根据index 文件进行检索会影响效率,所以还需要对上述文件进行切分,切分出来的单位叫做Segment (段 ),可以通过log.segment.bytes 来控制一段文件的大小。Segment的示意如下。
txt
00000000000000000000.index
00000000000000000000.log
00000000000000000000.timeindex
00000000000000050000.index
00000000000000050000.log
00000000000000050000.timeindex
四. 消费者组
消费者组是Kafka 提供的可扩展且具有容错性的消费者机制。一个消费者组内存在多个消费者,这些消费者共享一个Group ID。消费者组示意如下。
关于消费者组,有如下说明。
- 消费者组的不同消费者不能同时消费同一个 Partition;
- 如果消费者组里消费者数量和 Partition 数量一样则一个消费者消费一个 Partition;
- 如果消费者组里消费者数量大于 Partition 数量则部分消费者会无法消费到 Partition;
- 如果消费者组里消费者数量小于 Partition 数量则部分消费者会消费多个 Partition。
五. 偏移量
偏移量,即Consumer Offset ,用于存储消费者对Partition消费的位移量。
偏移量存储在Kafka 的内部Topic 中,这个内部Topic 叫做_consumer_offsets ,该Topic 默认有50 个分区,将消费者的Group ID 进行hash 后再对50取模,得到的结果对应的分区就会用于存储这个消费者的偏移量。
_ consumer_offsets的每条消息格式示意图如下所示。
注意_consumer_offsets 是存储在Broker上的。
六. 生产者发送消息完整流程
生产者发送消息完整流程图如下所示。
结合上述流程图,对消息发送流程说明如下。
- 生产者生成消息Record;
- Record经过拦截器链;
- key 和value进行序列化;
- 使用自定义或者默认的分区器获取Record 所属分区Partition;
- Record 放入消息累加器RecordAccumulator 。根据Topic 和Partition ,可以确定一个双端队列Deque ,该队列每个节点为多条Record 的合集即ProducerBatch ,新Record会被添加到队列最后一个节点上;
- Sender 将相同Broker 节点的可发送ProducerBatch 合并到一个Request 中并发送。Sender 会持续扫描RecordAccumulator 中的ProducerBatch ,只要满足大小为batch.size (默认 16K )或者最早Record 等待已经超过linger.ms ,该ProducerBatch 就会被Sender 收集,然后Sender 会合并收集的相同Broker 的ProducerBatch 到一个Request中并发送;
- 缓存请求Request 到inFlightRequest 缓冲区中。inFlightRequest 中为每个Broker 分配了一个队列,新Request 会添加到队列头,每个队列最多容纳的Request 个数由max.in.flight.requests.per.connection (默认为 5 )控制,队列满后不会生成新Request;
- Selector 发送请求到Broker;
- Broker 收到并处理Request 后,对Request 进行ACK;
- 客户端收到Request 的ACK 后,将Request 从inFlightRequest中移除。
七. 分区策略
Kafka中消息的分区计算策略小结如下。
- 消息中指定了分区。此时使用指定的分区;
- 消息中未指定分区但有自定义分区器。此时使用自定义分区器计算分区;
- 消息中未指定分区也没有自定义分区器但消息键不为空 。此时对键求哈希值,并用求得的哈希值对Topic的分区数取模得到分区;
- 如果前面都不满足 。此时根据Topic 取一个递增整数并对Topic分区数求模得到分区。
八. ISR机制
当生产者向服务端发送消息后,通常需要等待服务端的ACK,这一过程可以用下图进行示意。
即Producer 会将消息发送给Topic 对应分区的leader 节点,然后leader 与follower 进行同步,如果全部正常的follower 同步成功(follower 完成消息落盘 ),那么服务端就可以向Producer 发送ACK。
上面描述中的正常follower 的集合,叫做ISR (In-Sync Replica Set ),只有与leader 节点正常通信的follower 才会被放入ISR 中,换言之,只有ISR 中的follower 才有资格让leader等待同步结果。
如果leader 挂掉,那么会在ISR 中选择新的leader。
九. ACK机制
Producer 发送消息的时候,可以通过acks 配置项来决定服务端返回ACK的策略,如下所示。
- acks 设置为0 ,Producer 不需要等待服务端返回ACK ,即Producer不关心服务端是否成功将消息落盘;
- acks 设置为1 ,leader 成功将消息落盘便返回ACK,这是默认策略;
- acks 设置为 -1 ,leader 和ISR 中全部follower 落盘成功才返回ACK。
十. Segment生成策略
分区在磁盘上由多个Segment组成,如下所示。
有如下参数控制Segment的生成策略。
- log.segment.bytes 。用于设置单个Segment 大小,当某个Segment 的大小超过这个值后,就需要生成新的Segment;
- log.roll.hours 。用于设置每隔多少小时就生成新的Segment;
- log.index.size.max.bytes 。当Segment 的index 文件达到这个大小时,也需要生成新的Segment。
十一. index文件
使用Kafka 提供的kafka-dump-log.sh 工具,可以打开index 文件,打开后的index文件可以表示如下。
shell
offset: 613 position: 5252
offset: 1284 position: 10986
offset: 1803 position: 17491
offset: 2398 position: 25792
offset: 3422 position: 35309
offset: 4446 position: 51690
offset: 5470 position: 68071
offset: 6494 position: 84452
offset: 7518 position: 100833
上述示例中,offset 是偏移量,position 表示这个offset 对应的消息在log文件里的位置。
index文件建立的索引是稀疏索引,示意图如下。
十二. timeindex文件
每一条被发送的消息都会记录时间戳,这里的时间戳可以是发送消息时间戳,或者是消息落盘时间戳。可以配置如下。
- log.message.timestamp.type 设置为createtime。表示发送消息时间戳;
- log.message.timestamp.type 设置为logappendtime。表示消息落盘时间戳。
十三. 索引检索过程
- 根据offset 找到在哪个Segment中;
- 从Segment 的index 文件根据offset 找到消息的position;
- 根据position 从Segment 的log文件中最终找到消息。
十四. Partition存储总结
Partition存储示意图如下。
十五. Kafka中的 Controller选举
Controller 在Kafka 集群中负责对整个集群进行协调管理,比如完成分区分配 ,Leader 选举 和副本管理等。
关于Controller的选举,有如下注意点。
- 启动时选举 。集群中Broker 启动时会去Zookeeper 创建临时节点 /controller ,最先创建成功的Broker 会成为Controller;
- Controller 异常时选举 。如果Controller 挂掉,此时其它Broker 会通过Watch 对象收到Controller 变更的消息,然后就会尝试去Zookeeper 创建临时节点 /controller ,只会有一个Broker 创建成功,创建失败的Broker 会再次创建Watch 对象来监视新Controller;
- Borker 异常 。如果集群中某个非Controller 的Broker 挂掉,此时Controller 会检查挂掉的Broker 上是否有某个分区的leader 副本,如果有,则需要为这个分区选举新的leader 副本,并更新分区的ISR集合;
- Broker 加入 。如果有一个Broker 加入集群,则Controller 会去判断新加入的Broker 中是否含有当前已有分区的副本,如果有,那么需要去从leader副本中同步数据。
十六. 分区 leader副本选举
一个分区有三种集合,如下所示。
- AR (Assigned Replicas)。分区中的所有副本;
- ISR (In-Sync Replicas )。与leader 副本保持一定同步程度的副本,ISR 包括leader副本自身;
- OSR (Out-of-Sync Replicas )。与leader副本同步程度滞后过多的副本。
上述三种集合的关系是AR = ISR + OSR。
分区leader副本选举有如下注意点。
- leader 副本会维护和跟踪ISR 中所有副本与leader 副本的同步程度,如果某个副本的同步程度滞后过多,则leader 副本会将这个副本从ISR 中移到OSR中;
- 当OSR 中有副本重新与leader 副本保持一定同步程度,则leader 副本会将其从OSR 中移到ISR;
- 当leader 副本发生故障时,Controller 会负责为这个分区从ISR 中选举新的leader副本。
十七. 主从同步
分区的leader 和follower之间的主从同步示意图如下。
有两个重要概念如下所示。
- LEO (Log End Offset )。下一条待写入消息的Offset;
- HW (High Watermark )。ISR 集合中的最小LEO。
那么对于上图而言,HW 为6 ,那么消费者最多只能消费到HW 之前的消息,也就是Offset 为5的消息。
主从同步规则如下。
- follower 会向leader 发送fetch 请求,然后leader 向follower发送数据;
- follower 接收到数据后,依次写入消息并且更新LEO;
- leader 最后会更新HW。
当leader 或follower发生故障时,处理策略如下。
- 如果follower 挂掉,那么当follower 恢复后,需要先将HW 和HW 之后的数据丢弃,然后再向leader发起同步;
- 如果leader 挂掉,则会先从follower 中选择一个成为leader ,然后其它follower 把HW 和HW 之后的数据丢弃,然后再向leader发起同步。
十八. Kafka为什么快
- 顺序读写;
- 索引;
- 批量读写和文件压缩;
- 零拷贝。
十九. 零拷贝
如果要将磁盘中的文件内容,发送到远程服务器,则整个数据流转如下所示。
步骤说明如下。
- 从磁盘文件读取文件内容,并拷贝到内核缓冲区;
- CPU控制器将内核缓冲区的数据拷贝到用户缓冲区;
- 应用程序中调用write() 方法,将用户缓冲区的数据拷贝到Socket缓冲区;
- 将Socket缓冲区的数据拷贝到网卡。
一共经历了四次拷贝(和四次 CPU 上下文切换),其中如下两次拷贝是多余的。
- 内核缓冲区拷贝到用户缓冲区;
- 用户缓冲区拷贝到Socket缓冲区。
而Kafka中的零拷贝,就是将上述两次多余的拷贝省掉,示意图如下。
零拷贝步骤如下所示。
- 从磁盘文件读取文件内容,并拷贝到内核缓冲区;
- 将文件描述符和数据长度加载到Socket缓冲区;
- 将数据直接从内核缓冲区拷贝到网卡。
一共只会经历两次拷贝(和两次 CPU 上下文切换)。
总结不易,如果本文对你有帮助,烦请点赞,收藏加关注,谢谢帅气漂亮的你。