1. Kafka 和 ZooKeeper
Kafka 使用 Zookeeper 来维护集群成员的信息。每个 Broker 都有一个唯一标识符,这个标识符可以在配置文件里指定,也可以自动生成。在 Broker 启动的时间,它通过创建暂时节点把自己的 ID 注册到 Zookeeper。Kafka 组件订阅 Zookeeper 的 /broker/ids 路径,当有 Broker 加入集群或退出集群时,这些组件就可以获得通知。
如果要启动另一个具有相同 ID 的 Broker,会得到一个错误——新 Broker 会试着进行注册,但不会成功,由于 ZooKeeper 中已经有一个具有相同 ID 的 Broker。
在 Broker 停机、出现网络分区或长时间垃圾接纳停顿时,Broker 会与 ZooKeeper 断开连接,此时 Broker 在启动时创建的暂时节点会自动被 ZooKeeper 移除。监听 Broker 列表的 Kafka 组件会被告知 Broker 已移除。
Kafka 在 ZooKeeper 的关键存储信息:
- admin:存储管理信息。紧张为删除主题事件,分区迁移事件,优先副本选举,信息 (一般为暂时节点)
- brokers:存储 Broker 相关信息。broker 节点以及节点上的主题相关信息
- cluster:存储 kafka 集群信息
- config:存储 broker,client,topic,user 以及 changer 相关的配置信息
- consumers:存储消耗者相关信息
- controller:存储控制器节点信息
- controller_epoch:存储控制器节点当前的年事(说明控制器节点变更次数)
ZooKeeper 两个紧张特性:
- 客户端会话竣事时,ZooKeeper 就会删除暂时节点。
- 客户端注册监听它关心的节点,当节点状态发生变革(数据变革、子节点增减变革)时,ZooKeeper 服务会通知客户端。
2. 控制器
控制器(Controller),是 Apache Kafka 的核心组件。它的紧张作用是在 ZooKeeper 的帮助下管理和协调解个 Kafka 集群。控制器其实就是一个 Broker,只不过它除了具有一般 Broker 的功能以外,还负责 Leader 的选举。
2.1. 如何选举控制器
集群中任意一台 Broker 都能充当控制器的脚色,但是,在运行过程中,只能有一个 Broker 成为控制器,使用其管理和协调的职责。实际上,Broker 在启动时,会实行去 ZooKeeper 中创建 /controller 节点。Kafka 当前选举控制器的规则是:第一个在 ZooKeeper 成功创建 /controller 暂时节点的 Broker 会被指定为控制器。
选举控制器的详细流程:
基于 ZooKeeper 的控制器选举(传统模式)
- 第一个在 ZooKeeper 中成功创建 /controller 暂时节点的 Broker 会被指定为控制器。
- 其他 Broker 在控制器节点上创建 Zookeeper watch 对象。
- 如果控制器被关闭大概与 Zookeeper 断开连接,Zookeeper 暂时节点就会消失。集群中的其他 Broker 通过 watch 对象得到状态变革的通知,它们会实行让自己成为新的控制器。
- 第一个在 Zookeeper 里创建一个暂时节点 /controller 的 Broker 成为新控制器。其他 Broker 在新控制器节点上创建 Zookeeper watch 对象。
- 每个新选出的控制器通过 Zookeeper 的条件递增操作获得一个全新的、数值更大的 controller epoch。其他节点会忽略旧的 epoch 的消息。
- 当控制器发现一个 Broker 已离开集群,而且这个 Broker 是某些 Partition 的 Leader。此时,控制器会遍历这些 Partition,并用轮询方式确定谁应该成为新 Leader,随后,新 Leader 开始处理生产者和消耗者的请求,而 Follower 开始从 Leader 那里复制消息。
简而言之,Kafka 使用 Zookeeper 的暂时节点来选举控制器,并在节点加入集群或退出集群时通知控制器。控制器负责在节点加入或离开集群时进行 Partition Leader 选举。控制器使用 epoch 来制止“脑裂”,“脑裂”是指两个节点同时被以为自己是当前的控制器。
基于 KRaft 的控制器选举(无 ZooKeeper 模式)
Kafka 的 KRaft 模式(Kafka Raft)移除了对 ZooKeeper 的依赖,改用内部的 Raft 协议进行控制器选举。
- Raft 集群:
- Kafka 的元数据管理由一个内部的 Raft 集群负责。
- Raft 集群中的成员可以是 Broker,但不是所有 Broker 都是元数据节点。
- 选举机制:
- 在 Raft 协议中,控制器是 Raft 的 Leader 节点。
- 当集群启动或 Leader 失效时,所有 Follower 节点会发起选举,最终通过大多数(quorum)投票选出新的 Leader。
- 控制器职责:
- 控制器直接从 Raft 日志中读取或写入元数据,而不须要 ZooKeeper。
- 它与传统模式相比,淘汰了外部依赖,提升了系统的一致性和可用性。
控制器选举的优先级
Kafka 的控制器选举并没有特别的优先级规则,而是基于节点实行创建 /controller 节点的顺序(ZooKeeper 模式)或 Raft 的内部逻辑(KRaft 模式)。但是可以通过以下方式间接影响控制器选举:
- 优先选择性能较好的 Broker:
- 将性能较好的 Broker 启动得更早,增加其成为控制器的概率。
- 制止频繁控制器切换:
- 设置 zookeeper.session.timeout.ms 富足长,制止因短暂网络题目导致控制器切换。
- 监控和告警:
- 通过 Kafka 的监控工具检测控制器变更(如 kafka.controller:type=KafkaController,name=ActiveControllerCount),实时发现异常情况。
2.2. 控制器的作用
Topic 管理(创建、删除、增加分区)
这里的 Topic 管理,就是指控制器帮助我们完成对 Kafka Topic 的创建、删除以及分区增加的操作。换句话说,当我们执行 kafka-topics 脚本时,大部门的背景工作都是控制器来完成的。
#分区重分配
分区重分配紧张是指,kafka-reassign-partitions 脚本(关于这个脚本,后面我也会介绍)提供的对已有 Topic 分区进行细粒度的分配功能。这部门功能也是控制器实现的。
#选举 Leader
Preferred 领导者选举紧张是 Kafka 为了制止部门 Broker 负载过重而提供的一种换 Leader 的方案。在专栏后面说到工具的时间,我们再详谈 Preferred 领导者选举,这里你只须要了解这也是控制器的职责范围就可以了。
#集群成员管理
集群成员管理,包括自动检测新增 Broker、Broker 自动关闭及被动宕机。这种自动检测是依赖于前面提到的 Watch 功能和 ZooKeeper 暂时节点组合实现的。
比如,控制器组件会使用Watch 机制查抄 ZooKeeper 的 /brokers/ids 节点下的子节点数目变更。目前,当有新 Broker 启动后,它会在 /brokers 下创建专属的 znode 节点。一旦创建完毕,ZooKeeper 会通过 Watch 机制将消息通知推送给控制器,这样,控制器就能自动地感知到这个变革,进而开启后续的新增 Broker 作业。
侦测 Broker 存活性则是依赖于刚刚提到的另一个机制:暂时节点。每个 Broker 启动后,会在 /brokers/ids 下创建一个暂时 znode。当 Broker 宕机或自动关闭后,该 Broker 与 ZooKeeper 的会话竣事,这个 znode 会被自动删除。同理,ZooKeeper 的 Watch 机制将这一变更推送给控制器,这样控制器就能知道有 Broker 关闭或宕机了,从而进行“善后”。
#数据服务
控制器的最后一大类工作,就是向其他 Broker 提供数据服务。控制器上生存了最全的集群元数据信息,其他所有 Broker 会定期接收控制器发来的元数据更新请求,从而更新其内存中的缓存数据。
控制器中生存了多种数据,比较紧张的的数据有:
- 所有 Topic 信息。包括详细的分区信息,比如领导者副本是谁,ISR 集合中有哪些副本等。
- 所有 Broker 信息。包括当前都有哪些运行中的 Broker,哪些正在关闭中的 Broker 等。
- 所有涉及运维任务的分区。包括当前正在进行 Preferred 领导者选举以及分区重分配的分区列表。
值得注意的是,这些数据其实在 ZooKeeper 中也生存了一份。每当控制器初始化时,它都会从 ZooKeeper 上读取对应的元数据并填充到自己的缓存中。有了这些数据,控制器就能对外提供数据服务了。这里的对外紧张是指对其他 Broker 而言,控制器通过向这些 Broker 发送请求的方式将这些数据同步到其他 Broker 上。
2.3 控制器选举的常见题目
题目 1:频繁的控制器切换
- 原因:
- 控制器所在的 Broker 常常发生网络抖动或宕机。
- zookeeper.session.timeout.ms 设置过低。
- 解决方案:
- 进步 zookeeper.session.timeout.ms,例如从默认的 6 秒增加到 20 秒。
- 加强控制器所在节点的稳固性,使用高性能的硬件或更可靠的网络连接。
题目 2:控制器选举耗时过长
- 原因:
- ZooKeeper 性能瓶颈或 Broker 与 ZooKeeper 的连接延迟较高。
- Broker 启动速率较慢。
- 解决方案:
- 优化 ZooKeeper 的配置和性能(如增加 ZooKeeper 节点数目或升级硬件)。
- 确保 Broker 能快速启动并与 ZooKeeper 创建连接。
题目 3:控制器无法选出
- 原因:
- 所有 Broker 都无法与 ZooKeeper 创建连接。
- Raft 集群无法到达多数投票(KRaft 模式)。
- 解决方案:
- 查抄 ZooKeeper 集群是否正常运行。
- 在 KRaft 模式下,确保元数据节点数目为奇数,且大多数节点在线。
监控控制器选举
Kafka 提供了多种方式监控控制器状态和选举过程:
ZooKeeper 模式
- 查抄 ZooKeeper 的 /controller 节点:
- 使用 ZooKeeper CLI:
4. 共同其他参数优化
4.1 min.insync.replicas
- 作用:定义一个分区中至少须要与 Leader 同步的副本数目。
- 与 acks 的关系:
- acks=all 时,min.insync.replicas 见效。
- 如果同步副本数目小于 min.insync.replicas,生产者会收到错误,防止数据丢失。
- 配置示例: min.insync.replicas=2
4.2 retries
- 作用:生产者写入失败时的重试次数。
- 题目:
- 设置过低可能导致短暂的网络或副本故障影响消息发送。
- 设置过高可能造成大量重复数据写入。
- 推荐配置: retries=3
4.3 linger.ms
- 作用:控制生产者批量发送消息的等待时间。
- 优化方向:
linger.ms=5
5. 常见题目及解决方案
题目 1:数据丢失
- 原因:
- 使用 acks=0 或 acks=1。
- 副本数目少,且 Leader 副本发生故障。
- 解决方案:
- 使用 acks=all,确保数据复制到多个副本。
- 增加副本数目(Replication Factor)。
题目 2:生产者发送失败
- 原因:
- ISR 副本数目不敷,min.insync.replicas 未满足要求。
- 解决方案:
- 查抄 Broker 状态,确保 ISR 副本在线。
- 淘汰 min.insync.replicas(权衡数据可靠性)。
题目 3:延迟过高
- 原因:
- 使用 acks=all,等待副本同步。
- 网络或磁盘性能瓶颈。
- 解决方案:
- 优化网络和磁盘性能。
- 得当淘汰 acks 要求或副本数目(如果可以接受数据丢失风险)。
6. 推荐配置
高性能优先
适用于对数据可靠性要求较低的场景:
acks=1 min.insync.replicas=1 retries=3 linger.ms=1
高可靠性优先
适用于关键任务,确保数据不丢失:
acks=all min.insync.replicas=2 retries=5 linger.ms=5
均衡性能与可靠性
得当大多数应用场景:
acks=1 min.insync.replicas=1 retries=3 linger.ms=2
总结
Kafka 的 acks 参数在性能和可靠性之间提供了灵活的选择。对于高可靠性需求的应用,推荐 acks=all 并结合 min.insync.replicas。对于性能要求较高的场景,可以使用 acks=1 或 acks=0。详细设置需根据业务需求、集群规模以及容错本事进行权衡。
5.3. 消耗请求
Leader 处理拉取请求和处理生产请求的方式很相似:
- 请求须要先到达指定的 Partition Leader 上,然后客户端通过查询元数据来确保请求的路由是正确的。
- Leader 在收到请求时,会先查抄请求是否有用。
- 如果请求的偏移量存在,Broker 将按照客户端指定的数目上限从 Partition 里读取消息,再把消息返回给客户端。Kafka 使用零拷贝技术向客户端发送消息——也就是说,Kafka 直接把消息从文件(更准确的说,是文件系统缓存)里发送到网络通道,而不须要经过任何中间缓冲区。这制止了内存的字节拷贝和缓冲区维护,极大地进步了性能。
客户端可以指定 Broker 返回数据量的上限和下限,防止数据量过大造成客户端内存溢出。同时,客户端也可以指定返回的最小数据量,当消息数据量没有到达最小数据量时,请求会一直阻塞直到有富足的数据返回。指定最小的数据量在负载不高的情况下非常有用,通过这种方式可以减轻网络往返的额外开销。当然请求也不能永久的阻塞,客户端可以指定最大的阻塞时间,如果到达指定的阻塞时间,即便没有富足的数据也会返回。
不是所有 Leader 的数据都能够被读取。消耗者只能读取已提交的消息。只有当消息被写入分区的若干同步副本时,才被以为是已提交的。为什么是若干个 Broker 呢?这取决于你对“已提交”的定义。你可以选择只要 Leader 成功生存该消息就算是已提交,也可以是令所有 Broker 都成功生存该消息才算是已提交。
由于还没有被富足的副本持久化的消息,被以为是不安全的——如果 Leader 发生故障,另一个副本成为新的 Leader,这些消息就丢失了。如果允许读取这些消息,就可能会粉碎数据一致性。
这也意味着,如果 Broker 间的消息复制由于某些原因变慢了,那么消息到达消耗者的时间也会随之变长。延迟时间可以通过 replica.lag.time.max.ms 来配置,它指定了副本在复制消息时可被允许的最大延迟时间。
5.4. 其他请求
我们讨论了 Kafka 中最常见的三种请求类型:元信息请求,生产请求和拉取请求。这些请求都是使用的是 Kafka 的自定义二进制协议。集群中 Broker 间的通信请求也是使用同样的协议,这些请求是内部使用的,客户端不能发送。比如在选举 Partition Leader 过程中,控制器会发送 LeaderAndIsr 请求给新的 Leader 和其他跟随副本。
这个协议目前已经支持 20 种请求类型,而且仍然在演进以支持更多的类型。
6. 总结
6.1. 副本机制
- 每个 Partition 都有一个 Leader,零个或多个 Follower。
- Leader 处理一切对 Partition (分区)的读写请求;而 Follower 只需被动的同步 Leader 上的数据。
- 同一个 Topic 的差别 Partition 会分布在多个 Broker 上,而且一个 Partition 还会在其他的 Broker 上面进行备份。
6.2. 选举机制
Follower 宕机,啥事儿没有;Leader 宕机了,会从 Follower 中重新选举一个新的 Leader。 生产者/消耗者如何知道谁是 Leader
- Kafka 将这种元数据存储在 Zookeeper 服务中。
- 生产者和消耗者都和 Zookeeper 连接并通信。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|