集群的成员关系
Kafka使用Zookeeper维护集群的成员信息。
- 每一个
broker
都有一个唯一的标识,这个标识可以在配置文件中指定,也可以自动生成。 - 当
broker
在启动时通过创建Zookeeper的临时节点把自己的ID注册到Zookeeper中。broker、控制器和其他一些动态系统工具会订阅Zookeeper的/brokers/ids
路径;当有broker
加入或退出集群时,会收到通知。 - 当试图启动另一个具有相同ID的
broker
时,会收到错误信息。
控制器
控制器也是一个
broker
,除了提供一般broker功能外,还负责选举分区首领。
创建控制器
- 集群中第一个启动的broker会通过Zookeeper创建一个
/controller
的临时节点让自己成为控制器; - Zookeeper会为控制器分配一个
epoch
。 - 其他
broker
在启动时,也会尝试创建,但是因为已经存在他们会收到”节点已存在“异常; - 然后在控制器节点上创建
Zookeeper watch
,这样就可以接收这个节点的变更通知。通过这样的方式来保证节点只有一个控制器。
变更控制器
- 控制器关闭或者与Zookeeper断开连接,这个临时节点会消失;
- 当其他节点收到控制器节点消失的通知时,会尝试创建
/controller
的临时节点成为控制节点; - 其他未创建成功的broker会在新的控制节点上创建
Zookeeper watch
, - 新的控制器节点由Zookeeper分配一个数值更大的
epoch
。这样做的目的是为了杜绝之前离线的控制器重新上线,并且发送消息,如果broker接收到消息的epoch
小于监听的则会忽略当前消息。
新控制器 KRaft
用基于
Raft
的控制器替换基于Zookeeper的控制器。
集群即可以使用基于Zookeeper
的传统控制器,也可以使用KRaft
。
为什么替换控制器
- 元数据是同步写入Zookeeper的,但是异步发送给
broker
的,Zookeeper的接收更新也是异步的,会导致broker、控制器和Zookeeper之间元数据不一致的情况 - 控制器在重新启动时需要从Zookeeper读取所有的broker和分区元数据,再将他们发给所有broker,随着分区和broker的争夺,重启控制器会变慢。
- 元数据所有权架构不够好,有些操作通过控制器、有些通过broker、有些通过Zookeeper来完成
- 使用Kafka需要对Zookeeper有一定了解,学习成本较高
Zookeeper主要功能
- 用于选举控制器
- 保存集群元数据(broker、配置、主题、分区和副本)
KRaft
-
新架构中控制器节点形成了一个
Raft
仲裁,管理元数据事件日志,这个日志包含了集群元数据的每一个变更,原先保存在Zookeeper中的所有东西(主题、分区、ISR、配置等)都保存在这个日志中。 -
涉及直接与Zookeeper通信的客户端和broker操作都通过控制器来路由,以达到无缝迁移。
-
使用
Raft
算法,控制节点可以在不依赖外部系统情况下选举首领,首领节点被称为主控制器,负责处理来自所有broker的RPC的调用,跟随者控制器从主控制器复制数据,并会作为主控制器的热备, -
其他broker通过API从主控制器获取更新,而不是等待通知。broker将自己注册到控制器仲裁上,在注销前会一直保持注册状态。
复制
复制是Kafka架构核心的一部分,之所以这么重要,是因为他可以在个别节点失效时仍能保证Kafka的可用性和持久性。
Kafka中每个主题有若干分区,每个分区可以有多个副本,副本均匀的分布在多个broker中。
副本有两种类型
- 首领副本:每个分区都有一个首领副本,为了保证一致性,所有生产者的请求都会经过这个副本。客户端可以从首领副本或者跟随者副本读取数据
- 跟随者副本:除了首领副本以外都是跟随者副本。没特别指定,跟随者副本不处理来自客户端的请求,主要任务是从首领副本复制消息,保持与首领一致的状态。
请求的处理
客户端持有集群的元数据缓存,元数据中包含了客户端感兴趣的主题清单以及主题包含的分区、副本、首领等,一般情况下客户端会直接向目标broker发送生产请求和获取请求。
请求分类
- 生产请求
- 获取请求
- 管理请求
生产请求
生产者发送的请求,包含客户端要写入broker的消息
borker在接收到生产请求时会做一些验证
- 发送数据的用户是否有写入权限
- 请求中
acks
参数是否有效 - 如果
acks=all
是否足够多的同步副本保证消息已经写入
消息写入分区首领后,broker会检查acks
参数,等到所有的都完成后,会返回响应给客户端。
获取请求
消费者和跟随者副本发送的请求,用于从broker读取消息。
broker接收到获取请求时会做一些校验
- 请求指定的偏移量是否存在
客户端读取消息时,Kafka使用零复制技术向客户端发送消息。也就是说Kafka会直接把消息从文件里发送到网路通道,不经过任何缓冲区。
客户端能读取的消息是已经被写入所有同步副本的消息;部分没有完全同步给所有副本的消息是不会发送给消费者的。
管理请求
管理客户端发送的请求,用于执行元数据操作,比如创建和删除topic
存储
分层存储
- 本次存储:与当前存储一致,保存在broker机器上
- 优势:响应快
- 劣势:成本高、数据保留时间短
- 远程存储:利用HDFS、S3等存储系统来存储日志信息
- 优势:成本低于本地存储、数据可保留较长时间
- 劣势:响应较慢
文件管理
数据保留是Kafka的一个重要概念
- Kafka中一个分区会分为若干片段,
- 默认每个片段包含1GB或者1周的数据,触发任意上限,会关闭当前文件,重新打开一个文件
- 正在写入的片段叫做活动片段,活动片段不会被删除。
压实
保留每个键的最新有效数据,同时清理历史冗余的数据。
- 保留最新值:对于每条消息,如果指定了 Key,Kafka 会为每个 Key 保留最后一个写入的 Value(最新状态)。
- 删除冗余记录:所有旧版本的 Key-Value 对会被标记为可删除(逻辑删除),但物理删除会在后台异步完成。
- 非键消息的保留:没有 Key 的消息(或 Key 为 null 的消息)不会被压实,仍然遵循基于时间或大小的保留策略(例如 7 天后删除)。
什么时候压实主题
- 通过
log.cleaner.enabled
参数启动压实线程,线程会选择浑浊率最高的分区来压实。 - 默认情况下会在主题中有50%数据包含脏记录时进行压实。
- 每个日志片段分为两个部分
- 干净的部分:被压实过的消息,每个键只有一个对应得值,是上一次压实保留下来得
- 浑浊部分:上一次压实之后写入得