✨Kafka✨基础知识全面总结

为心爱的人努力是一种幸福

前言

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 ,副本数为3Topic

一个分区可以有多个副本,且一个分区的多个副本不能在同一个Broker 上,例如只有3Broker ,但是创建Topic 的时候,指定副本数为4 ,此时创建Topic会失败。

分区的多个副本可以分为leader 节点和follower 节点,且leader 节点为客户端提供读写 功能,follower 节点会从leader节点同步数据但不提供读写功能,这样能避免出现读写不一致的问题。

创建Topic 时,会在Broker 上为这个Topic的分区创建目录,如下所示。

分区目录的内容如下所示。

每个文件含义如下。

  1. index 文件是索引文件。记录消息的编号;
  2. timeindex 文件是时间戳索引。记录生产者发送消息的时间和消息记录到日志文件的时间;
  3. log 是日志文件。记录消息。

因为log 文件会越来越大,此时根据index 文件进行检索会影响效率,所以还需要对上述文件进行切分,切分出来的单位叫做Segment ),可以通过log.segment.bytes 来控制一段文件的大小。Segment的示意如下。

txt 复制代码
00000000000000000000.index
00000000000000000000.log
00000000000000000000.timeindex

00000000000000050000.index
00000000000000050000.log
00000000000000050000.timeindex

四. 消费者组

消费者组是Kafka 提供的可扩展且具有容错性的消费者机制。一个消费者组内存在多个消费者,这些消费者共享一个Group ID。消费者组示意如下。

关于消费者组,有如下说明。

  1. 消费者组的不同消费者不能同时消费同一个 Partition
  2. 如果消费者组里消费者数量和 Partition 数量一样则一个消费者消费一个 Partition
  3. 如果消费者组里消费者数量大于 Partition 数量则部分消费者会无法消费到 Partition
  4. 如果消费者组里消费者数量小于 Partition 数量则部分消费者会消费多个 Partition

五. 偏移量

偏移量,即Consumer Offset ,用于存储消费者对Partition消费的位移量。

偏移量存储在Kafka 的内部Topic 中,这个内部Topic 叫做_consumer_offsets ,该Topic 默认有50 个分区,将消费者的Group ID 进行hash 后再对50取模,得到的结果对应的分区就会用于存储这个消费者的偏移量。

_ consumer_offsets的每条消息格式示意图如下所示。

注意_consumer_offsets 是存储在Broker上的。

六. 生产者发送消息完整流程

生产者发送消息完整流程图如下所示。

结合上述流程图,对消息发送流程说明如下。

  1. 生产者生成消息Record
  2. Record经过拦截器链;
  3. keyvalue进行序列化;
  4. 使用自定义或者默认的分区器获取Record 所属分区Partition
  5. Record 放入消息累加器RecordAccumulator 。根据TopicPartition ,可以确定一个双端队列Deque ,该队列每个节点为多条Record 的合集即ProducerBatch ,新Record会被添加到队列最后一个节点上;
  6. Sender 将相同Broker 节点的可发送ProducerBatch 合并到一个Request 中并发送。Sender 会持续扫描RecordAccumulator 中的ProducerBatch ,只要满足大小为batch.size默认 16K )或者最早Record 等待已经超过linger.ms ,该ProducerBatch 就会被Sender 收集,然后Sender 会合并收集的相同BrokerProducerBatch 到一个Request中并发送;
  7. 缓存请求RequestinFlightRequest 缓冲区中。inFlightRequest 中为每个Broker 分配了一个队列,新Request 会添加到队列头,每个队列最多容纳的Request 个数由max.in.flight.requests.per.connection默认为 5 )控制,队列满后不会生成新Request
  8. Selector 发送请求到Broker
  9. Broker 收到并处理Request 后,对Request 进行ACK
  10. 客户端收到RequestACK 后,将RequestinFlightRequest中移除。

七. 分区策略

Kafka中消息的分区计算策略小结如下。

  1. 消息中指定了分区。此时使用指定的分区;
  2. 消息中未指定分区但有自定义分区器。此时使用自定义分区器计算分区;
  3. 消息中未指定分区也没有自定义分区器但消息键不为空 。此时对键求哈希值,并用求得的哈希值对Topic的分区数取模得到分区;
  4. 如果前面都不满足 。此时根据Topic 取一个递增整数并对Topic分区数求模得到分区。

八. ISR机制

当生产者向服务端发送消息后,通常需要等待服务端的ACK,这一过程可以用下图进行示意。

Producer 会将消息发送给Topic 对应分区的leader 节点,然后leaderfollower 进行同步,如果全部正常的follower 同步成功(follower 完成消息落盘 ),那么服务端就可以向Producer 发送ACK

上面描述中的正常follower 的集合,叫做ISRIn-Sync Replica Set ),只有与leader 节点正常通信的follower 才会被放入ISR 中,换言之,只有ISR 中的follower 才有资格让leader等待同步结果。

如果leader 挂掉,那么会在ISR 中选择新的leader

九. ACK机制

Producer 发送消息的时候,可以通过acks 配置项来决定服务端返回ACK的策略,如下所示。

  1. acks 设置为0Producer 不需要等待服务端返回ACK ,即Producer不关心服务端是否成功将消息落盘;
  2. acks 设置为1leader 成功将消息落盘便返回ACK,这是默认策略;
  3. acks 设置为 -1leaderISR 中全部follower 落盘成功才返回ACK

十. Segment生成策略

分区在磁盘上由多个Segment组成,如下所示。

有如下参数控制Segment的生成策略。

  1. log.segment.bytes 。用于设置单个Segment 大小,当某个Segment 的大小超过这个值后,就需要生成新的Segment
  2. log.roll.hours 。用于设置每隔多少小时就生成新的Segment
  3. log.index.size.max.bytes 。当Segmentindex 文件达到这个大小时,也需要生成新的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文件

每一条被发送的消息都会记录时间戳,这里的时间戳可以是发送消息时间戳,或者是消息落盘时间戳。可以配置如下。

  1. log.message.timestamp.type 设置为createtime。表示发送消息时间戳;
  2. log.message.timestamp.type 设置为logappendtime。表示消息落盘时间戳。

十三. 索引检索过程

  1. 根据offset 找到在哪个Segment中;
  2. Segmentindex 文件根据offset 找到消息的position
  3. 根据positionSegmentlog文件中最终找到消息。

十四. Partition存储总结

Partition存储示意图如下。

十五. Kafka中的 Controller选举

ControllerKafka 集群中负责对整个集群进行协调管理,比如完成分区分配Leader 选举副本管理等。

关于Controller的选举,有如下注意点。

  1. 启动时选举 。集群中Broker 启动时会去Zookeeper 创建临时节点 /controller ,最先创建成功的Broker 会成为Controller
  2. Controller 异常时选举 。如果Controller 挂掉,此时其它Broker 会通过Watch 对象收到Controller 变更的消息,然后就会尝试去Zookeeper 创建临时节点 /controller ,只会有一个Broker 创建成功,创建失败的Broker 会再次创建Watch 对象来监视新Controller
  3. Borker 异常 。如果集群中某个非ControllerBroker 挂掉,此时Controller 会检查挂掉的Broker 上是否有某个分区的leader 副本,如果有,则需要为这个分区选举新的leader 副本,并更新分区的ISR集合;
  4. Broker 加入 。如果有一个Broker 加入集群,则Controller 会去判断新加入的Broker 中是否含有当前已有分区的副本,如果有,那么需要去从leader副本中同步数据。

十六. 分区 leader副本选举

一个分区有三种集合,如下所示。

  1. ARAssigned Replicas)。分区中的所有副本;
  2. ISRIn-Sync Replicas )。与leader 副本保持一定同步程度的副本,ISR 包括leader副本自身;
  3. OSROut-of-Sync Replicas )。与leader副本同步程度滞后过多的副本。

上述三种集合的关系是AR = ISR + OSR

分区leader副本选举有如下注意点。

  1. leader 副本会维护和跟踪ISR 中所有副本与leader 副本的同步程度,如果某个副本的同步程度滞后过多,则leader 副本会将这个副本从ISR 中移到OSR中;
  2. OSR 中有副本重新与leader 副本保持一定同步程度,则leader 副本会将其从OSR 中移到ISR
  3. leader 副本发生故障时,Controller 会负责为这个分区从ISR 中选举新的leader副本。

十七. 主从同步

分区的leaderfollower之间的主从同步示意图如下。

有两个重要概念如下所示。

  1. LEOLog End Offset )。下一条待写入消息的Offset
  2. HWHigh Watermark )。ISR 集合中的最小LEO

那么对于上图而言,HW6 ,那么消费者最多只能消费到HW 之前的消息,也就是Offset5的消息。

主从同步规则如下。

  1. follower 会向leader 发送fetch 请求,然后leaderfollower发送数据;
  2. follower 接收到数据后,依次写入消息并且更新LEO
  3. leader 最后会更新HW

leaderfollower发生故障时,处理策略如下。

  1. 如果follower 挂掉,那么当follower 恢复后,需要先将HWHW 之后的数据丢弃,然后再向leader发起同步;
  2. 如果leader 挂掉,则会先从follower 中选择一个成为leader ,然后其它followerHWHW 之后的数据丢弃,然后再向leader发起同步。

十八. Kafka为什么快

  1. 顺序读写
  2. 索引
  3. 批量读写和文件压缩
  4. 零拷贝

十九. 零拷贝

如果要将磁盘中的文件内容,发送到远程服务器,则整个数据流转如下所示。

步骤说明如下。

  1. 从磁盘文件读取文件内容,并拷贝到内核缓冲区;
  2. CPU控制器将内核缓冲区的数据拷贝到用户缓冲区;
  3. 应用程序中调用write() 方法,将用户缓冲区的数据拷贝到Socket缓冲区;
  4. Socket缓冲区的数据拷贝到网卡。

一共经历了四次拷贝(和四次 CPU 上下文切换),其中如下两次拷贝是多余的。

  1. 内核缓冲区拷贝到用户缓冲区;
  2. 用户缓冲区拷贝到Socket缓冲区。

Kafka中的零拷贝,就是将上述两次多余的拷贝省掉,示意图如下。

零拷贝步骤如下所示。

  1. 从磁盘文件读取文件内容,并拷贝到内核缓冲区;
  2. 将文件描述符和数据长度加载到Socket缓冲区;
  3. 将数据直接从内核缓冲区拷贝到网卡。

一共只会经历两次拷贝(和两次 CPU 上下文切换)。


总结不易,如果本文对你有帮助,烦请点赞,收藏加关注,谢谢帅气漂亮的你。

相关推荐
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2342 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟3 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity4 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天4 小时前
java的threadlocal为何内存泄漏
java