Kafka服务实例,负责消息的持久化、中转等功能。一个独立的Kafka 服务器被就是一个broker。
broker 是集群的组成部分。每个集群都有一个broker 同时充当了集群控制器Controller的角色。
kafka使用Zookeeper(ZK)进行元数据管理,保存broker注册信息,包括主题(Topic)、分区(Partition)信息等,选择分区leader,在低版本kafka消费者的offset信息也会保存在ZK中。
在每一个Broker在启动时都会像向ZK注册信息,ZK会选取一个最早注册的Broker作为Controller,后面Controller会与ZK进行数据交互获取元数据(即整个Kafka集群的信息,例如有那些Broker,每个Broker中有那些Partition等信息),并负责管理工作,包括将分区分配给broker 和监控broker,其他Broker是与Controller进行交互进而感知到整个集群的所有信息。
流程如下:
介绍完 Broker 的工作流程,给大家总结一下,Broker 在工作过程中涉及到的一些参数:
参数 | 描述 |
---|---|
replica.lag.time.max.ms | ISR 中,如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR,默认 30s |
auto.leader.rebalance.enable | 默认是 true。自动 Leader Partition 平衡 |
leader.imbalance.per.broker.percentage | 默认是 10%。每个 broker 允许的不平衡的 leader 的比率。如果每个 broker 超过了这个值,会触发 leader 的平衡 |
leader.imbalance.check.interval.seconds | 默认值 300 秒。检查 leader 负载是否平衡的间隔时间 |
log.segment.bytes | Kafka 中 log 日志是分成一块块存储的,此配置是指 log 日志划分成块的大小,默认值 1G |
log.index.interval.bytes | 默认 4kb,每当写入了 4kb 大小的日志 (.log),然后就往 index 文件里面记录一个索引 |
log.retention.hours | Kafka 中数据保存的时间,默认 7 天 |
log.retention.minutes | Kafka 中数据保存的时间,分钟级别,默认关闭 |
log.retention.ms | Kafka 中数据保存的时间,毫秒级别,默认关闭 |
log.retention.check.interval.ms | 检查数据是否保存超时的间隔,默认是 5 分钟 |
log.retention.bytes | 默认等于 -1,表示无穷大。超过设置的所有日志总大小,删除最早的 segment |
log.cleanup.policy | 默认是 delete,表示所有数据启用删除策略; 如果设置值为 compact,表示所有数据启用压缩策略 |
num.io.threads | 默认是 8。负责写磁盘的线程数。整个参数值要占总核数的 50% |
num.replica.fetchers | 副本拉取线程数,这个参数占总核数的 50%的 1/3 |
num.network.threads | 默认是 3。数据传输线程数,这个参数占总核数的 50%的 2/3 |
log.flush.interval.messages | 强制页缓存刷写到磁盘的条数,默认是 long 的最大值。不建议修改 |
log.flush.interval.ms | 每隔多久刷数据到磁盘,默认是 null。不建议修改 |
在Kafka早期版本,对于分区和副本的状态的管理依赖于zookeeper的Watcher和队列:每一个broker都会在zookeeper注册Watcher,所以zookeeper就会出现大量的Watcher, 如果宕机的broker上的partition很多比较多,会造成多个Watcher触发,造成集群内大规模调整;每一个replica都要去再次zookeeper上注册监视器,当集群规模很大的时候,zookeeper负担很重。这种设计很容易出现脑裂和羊群效应以及zookeeper集群过载。
新的版本中该变了这种设计,使用KafkaController,只有KafkaController,Leader会向zookeeper上注册Watcher,其他broker几乎不用监听zookeeper的状态变化。
Kafka集群中多个broker,有一个会被选举为controller leader,负责管理整个集群中分区和副本的状态,比如partition的leader 副本故障,由controller 负责为该partition重新选举新的leader 副本;当检测到ISR列表发生变化,有controller通知集群中所有broker更新其MetadataCache信息;或者增加某个topic分区的时候也会由controller管理分区的重新分配工作。
控制器在 Kafka 中是起协调作用的组件,那么控制器的作用大致可以分为 5 种:
1.Topic管理
这里说的To这里说的Topic管理,是指控制器帮助我们完成对 Kafka 主题的创建、删除以及分区增加的操作, 大部分的后台工作都是控制器来完成的。
2.分区重分配
当一个新的 broker 刚加入集群时,不会自动地分担己有 topic 的负载,它只会对后续新增的 topic 生效。
如果要让新增 broker 为己有的 topic 服务,用户必须手动地调整现有的 topic 的分区分布,将一部分分区搬移到新增 broker 上。这就是所谓的分区重分配reassignment
操作。
除了处理 broker 扩容导致的不均衡之外,再均衡还能用于处理 broker 存储负载不均衡的情况,在单个或多个 broker 之间的日志目录之间重新分配分区。用于解决多个代理之间的存储负载不平衡。
3.Leader选
触发分区 leader 选举的几种场景:
当上述几种情况发生时,Controller 会遍历所有相关的主题分区并为其指定新的 Leader。然后向所有包含相关主题分区的 Broker 发送更新请求,其中包含了最新的 Leader 与 Follower 副本分配信息。待更新完毕后,新 Leader 会开始处理来自生产者和消费者的请求,而 Follower 开始从新 Leader 那里复制消息。
4.集群成员管理
这是控制器提供的集群成员管理功能, 主要包括自动检测新增 Broker、Broker 主动关闭及被动宕机, 而这种自动检测主要是依赖于 Watch 通知功能和 ZooKeeper 临时节点组合实现的。
比如,控制器组件会利用 Watch 机制 检查 ZooKeeper 的 /brokers/ids 节点下的子节点数量变更。目前,当有新 Broker 启动后,它会在 /brokers 下创建 临时的 znode 节点。一旦创建完毕,ZooKeeper 会通过 Watch 机制将消息通知推送给控制器,这样,控制器就能自动地感知到这个变化。
5.提供数据服务
控制器会向其他 Broker 提供数据服务。控制器上保存了最全的集群元数据信息,其他所有 Broker 会定期接收控制器发来的元数据更新请求,从而更新其内存中的缓存数据。
上面说了Controller会提供Broker节点及Topic等数据的信息,下面具体能提供哪些:
从上面表格可以看出,存储的大概有3大类:
数据实际存储在ZK之中。
其中/brokers/ids/[0...n]
:是使用临时节点存储在线的是各个服务节点的信息,当下线后自动删除;
/brokers/seqid
:辅助生成的brokerId,当用户没有配置broker.id时,ZK会自动生成一个全局唯一的id。
/brokers/topics/{topicName}
:持久化数据节点存储topic的分区副本分配信息;
在/brokers/topics/{topicName}//partitions/0/state
中记录了leader
和isr
队列的内容;
这里需要先明确一个概念leader选举,因为kafka中涉及多处选举机制,容易搞混,kafka由三个方面会涉及到选举:
这里我们说的是 leader选举是controller的选举。
Broker 在启动时,会尝试去 ZooKeeper 中创建 /controller 节点。Kafka 当前选举控制器的规则是:第一个成功创建 /controller 节点的 Broker 会被指定为控制器。
但是当Controller节点挂了之后需要选举出新的controller。
Broker的leader选举过程与故障处理:
示例:
图片来源:https://mp.weixin.qq.com/s/tTKXb6On5bfJGjcvn9IpbA
如上图所示: 最开始时候,Broker 0 是控制器。当 Broker 0 被检测宕机后,ZooKeeper 通过 Watch 机制感知到并删除了 /controller 临时节点。之后,所有还存活的 Broker 开始竞选新的控制器。这时 Broker 3 最终赢得了选举,成功地在 ZooKeeper 上重建了 /controller 临时节点。Broker 3 会从 ZooKeeper 中读取集群元数据信息,并初始化到自己的缓存中。控制器的 Failover 完成,这时候就可以正常工作了。
所谓的脑裂问题也就是出现了多个大脑,就是leader。比如因为网络或者其他的原因导致leader出现假死状态,此时会触发leader选举,这样就会出现两个leader。
broker的leader主要是用于管理主题的,那些发生脑裂之后创建主题、增加分区的操作都会报错;但是现有的主题的读写是不影响的,这是因为读写是获取分区的元数据在任意一个broker中有可以拿到。
脑裂如何产生?
broker是通过抢占的方式在zookeeper中注册临时节点/controller来时实现的,先到先得。
由于zookeeper的临时节点的有效性是通过session来判断的,若在session timeout时间内:
则会出现脑裂问题。
kafka的解决方案
为了解决Controller脑裂问题,zookeeper中有一个持久节点/controller_epoch,存放的是一个整形值的epoch number(纪元编号,也称为隔离令牌),集群中每选举一次控制器,就会通过Zookeeper创建一个数值更大的epoch number,如果有broker收到比这个epoch数值小的数据,就会忽略消息。
在早期的kafka版本中,如果宕机的那个Broker上的Partition比较多, 会造成多个Watch被触发,造成集群内大量的调整,导致大量网络阻塞,这种羊群效应会导致zookeeper过载的隐患。
之后kafka引入controller的概念(也就是broker的leader)来对分区副本的状态进行管理,当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本。当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有broker更新其元数据信息。
在使用zookeeper的分布式中,这种脑裂和羊群效应都是不可避免的。
现阶段的kakfa集群中,只需要broker的leader在zookeeper
去注册相应的监听器,其他的broker
很少去监听zookeeper的数据变化,但是每个broker还是需要对/controller
进行监听;当/controller
节点发生数据变化的时候,每个broker都会更新自身内存中保存的activeControllerId
。
当/controller
节点被删除时,集群中的broker会进行选举,如果broker在节点被删除前是控制器的话,在选举前还需要有一个下线的操作(关闭相应的资源,比如关闭状态机、注销相应的监听器等)。如果有特殊需要,可以手动删除/controller
节点来触发新一轮的选举。当然关闭控制器所对应的broker以及手动向/controller
节点写入新的brokerid
的所对应的数据同样可以触发新一轮的选举。
参考:https://mp.weixin.qq.com/s/uI2zkf74KXsWaCOplX1Ing
https://mp.weixin.qq.com/s/oxb2Ezn4K2jMPzubqFULuw
https://mp.weixin.qq.com/s/tTKXb6On5bfJGjcvn9IpbA
https://mp.weixin.qq.com/s/CCAP8n0mTCrUT-NzOAacCg