基于kafka 2.12-2.0.0版本

kafka-clients 2.0.0

本文是《深入理解Kafka核心设计与实践原理》的读书笔记、再均衡部分有在网上找资料。

kafka在zookeeper上写的节点

kafka常用客户端依赖 kafka 服务端_控制器

 

一、Broker

1.Broker处理请求流程

kafka常用客户端依赖 kafka 服务端_broker_02

 

在Kafka的架构中,会有很多客户端向Broker端发送请求,Kafka 的 Broker 端有个 SocketServer 组件,用来和客户端建立连接,然后通过Acceptor线程来进行请求的分发,由于Acceptor不涉及具体的逻辑处理,非常得轻量级,因此有很高的吞吐量。

接着Acceptor 线程采用轮询的方式将入站请求公平地发到所有网络线程中,网络线程池默认大小是 3个,表示每台 Broker 启动时会创建 3 个网络线程,专门处理客户端发送的请求,可以通过Broker 端参数 num.network.threads来进行修改。

那么接下来处理网络线程处理流程如下:

kafka常用客户端依赖 kafka 服务端_broker_03

当网络线程拿到请求后,会将请求放入到一个共享请求队列中。Broker 端还有个 IO 线程池,负责从该队列中取出请求,执行真正的处理。如果是 PRODUCE 生产请求,则将消息写入到底层的磁盘日志中;如果是 FETCH 请求,则从磁盘或页缓存中读取消息。

IO 线程池处中的线程是执行请求逻辑的线程,默认是8,表示每台 Broker 启动后自动创建 8 个 IO 线程处理请求,可以通过Broker 端参数 num.io.threads调整。

Purgatory组件是用来缓存延时请求(Delayed Request)的。比如设置了 acks=all 的 PRODUCE 请求,一旦设置了 acks=all,那么该请求就必须等待 ISR 中所有副本都接收了消息后才能返回,此时处理该请求的 IO 线程就必须等待其他 Broker 的写入结果。

二、控制器Controller

在 Kafka 集群中会有一个或多个 broker,其中有一个 broker 会被选举为控制器(Kafka Controller),它负责管理整个集群中所有分区和副本的状态

在任意时刻集群中有且仅有一个Broker是控制器。

每个Broker都会在内存中保存当前控制器的brokerid的值,这个值可以标识为activeControllerId.

1.控制器的职责

2.1.1.监听分区相关的变化。

为Zookeeper中的/admin/reassign_partitions节点注册PartitionReassignmentHandler,用来处理分区重分配的动作。

为Zookeeper中的/isr_change_notification节点注册IsrChangeNotificetionHandler,用来处理ISR集合变更的动作。

为Zookeeper中的/admin/preferred-replica-election节点添加PreferredReplicaElectionHandler,用来处理优先副本的选举动作。

2.1.2.监听主题相关的变化。

为Zookeeper中的/brokers/topics节点添加TopicChangeHandler,用来处理主题增减的变化。

为Zookeeper中的/admin/delete_topics节点添加TopicDeletionHandler,用来处理删除主题的动作。

2.1.3.监听broker相关的变化。

为Zookeeper中的/brokers/ids节点添加BrokerChangeHandler,用来处理broker增减的变化。

控制器组件会利用 Watch 机制检查 ZooKeeper 的 /brokers/ids 节点下的子节点数量变更。

当有新Broker启动后,新Broker会在 /brokers 下创建专属的 znode 节点。

一旦创建完毕,ZooKeeper 会通过 Watch 机制将消息通知推送给控制器,控制器就能自动地感知到这个变化,进而开启后续的新增 Broker 作业。

2.1.4.从Zookeeper中读取当前所有与主题、分区、几broker有关的信息并进行相应的管理。

对所有主题对应的Zookeeper中的/brokers/topics/<topic>节点添加PartitionModificationsHandler,用来监听主题中的分区分配的变化

2.1.5.启动并管理分区状态机和副本状态机

2.1.6.更新集群的元数据信息

维护上下文信息(ControllerContext) ,这些变更信息同步到其他普通的broker节点中

2.1.7.如果参数auto.leader.rebalance.enable设置为true,则还会开启一个名为"auto-leader-rebalance-task"的定时任务来负责维护分区的优先副本的均衡

Preferred 领导者选举主要是 Kafka 为了避免部分 Broker 负载过重而提供的一种换 Leader 的方案。

2.1.8 分区Leader的选举

1.OfflinePartitionLeaderElectionStrategy策略

分区leader副本的选举由控制器负责具体实施。当创建分区(创建主题或增加分区都由创建分区的动作)或分区上线(比如分区中原先的leader副本下线,此时分区需要需那句一个新的leader上线来对外提供服务)的时候都需要执行leader的选举动作,对应的选举策略为OfflinePartitionLeaderElectionStrategy。

这种策略的基本思路时按照AR集合中副本的顺序查找第一个存货的副本,并且这个副本在ISR集合中。

如果ISR集合中没有可用的副本,那么此时还要检查一下所配置的unclean.leader.election.enable参数(默认为false),如果这个参数配置为true,那么表示允许从非ISR列表种选举leader,即从AR列表中找到的第一次存活的副本即为leader

一个分区的AR集合在分配的时候就被指定,并且只要不发生重分配的情况,集合内部副本的顺序时保持不变的,而分区的ISR集合中副本的顺序可能会改变

2.ReassignPartitionLeaderElectionStrategy策略

当分区进行重新分配的时候也需要执行Leader的选举动作,此时使用的是ReassignPartitionLeaderElectionStrategy策略。

这个选举策略的思路是:从重分配的AR列表中找到第一个存活的副本,且这个副本目前在ISR列表中

3.PreferredReplicaPartitionLeaderElectionStrategy策略

当发生优先副本的选举时,直接将优先副本设置为leader即可,AR集合中的第一个副本即为优先副本

4.ControlledShutdownPartitionLeaderElectionStrategy策略

当某个节点被优雅地关闭(也就是执行ControlledShutdown)时,位于这个节点上的leader副本都会下线,所以与此对应的分区需要执行从AR列表中找到第一个存活的副本,且这个副本在目前的ISR列表中,与此同时还要确保这个副本不处于正在被关闭的节点上。

控制器上保存了最全的集群元数据信息,见下图

kafka常用客户端依赖 kafka 服务端_控制器_04

2.启动时控制器的竞争

流程:

每个Broker 在启动时,会尝试去读取 ZooKeeper 中创建 /controller 节点的brokerId的值,如果读取到的值不为-1,表示已经有其他的Broker节点成功竞选为控制器,所以当前Broker会放弃竞选。

如果Zookeeper中不存在 /controller 节点,或者这个节点中的数据异常,那么就回去尝试创建 /controller 节点。

只有成功创建/controller 节点的那个broker才会成为控制器

/controller 节点内容

kafka常用客户端依赖 kafka 服务端_kafka常用客户端依赖_05

其中version在目前版本固定为1,

brokerid表示成为控制器的broker的id,

timestamp表示竞选成为控制器时的时间戳 

3.控制器的纪元

ZooKeeper中的 /controller_epoch

controller_epoch 用于记录控制器发生变更的次数,即记录当前的控制器是第几代控制器,我们也可以称之为“控制器的纪元”。

controller_epoch 的初始值为1,即集群中第一个控制器的纪元为1,当控制器发生变更时,每选出一个新的控制器就将该字段值加1。

Kafka 通过 controller_epoch 来保证控制器的唯一性,进而保证相关操作的一致性。

每个和控制器交互的请求都会携带 controller_epoch 这个字段,如果请求的 controller_epoch 值小于内存中的 controller_epoch 值,则认为这个请求是向已经过期的控制器所发送的请求,那么这个请求会被认定为无效的请求。

如果请求的 controller_epoch 值大于内存中的 controller_epoch 值,那么说明已经有新的控制器当选了。

4.维护上下文信息(ControllerContext)

控制器在选举成功后会读取Zookeeper中各个节点的数据来初始化上下文信息(ControllerContext),并且需要管理这些上下文信息。

比如为某个主题增加了若干分区,控制器在负责创建这些分区的同时要更新上下文信息,并且需要将这些变更信息同步到其他普通的broker节点中。

不管是监听器出发的事件,还是定时任务出发的事件,或者是其他事件(比如ControlledShutdown)都会读取或更新控制器中的上下文信息。

那么这就会涉及到多线程间的同步。如果单纯使用锁机制来实现,那么整体的性能就会大打折扣。

针对这一现象,Kafka的控制器使用单线程基于事件队列的模型

1.将每个事件都做一层封装,然后按照事件发生的先后顺序暂存到LinkedBlockingQueue中,

2.最后使用一个专用的线程ControllerEventThread按照FIFO先入先出的原则顺序处理各个事件,

这样不需要锁机制就可以在多线程间维护线程安全。

kafka常用客户端依赖 kafka 服务端_kafka常用客户端依赖_06

5.使用Broker控制器的好处

早期的Kafka版本中,并灭有采用Kafka Controller控制器这样一个概念来对分区和副本的状态进行管理,而是每个broker都会在ZooKeeper上为分区和副本注册大量的监听器(Watcher)。

这样的话,当分区或副本状态变化时会唤醒很多不必要的监听器,这种严重依赖ZooKeeper的设计会有脑裂、羊群效应,以及造成ZooKeeper过载的隐患。

目前的新版本的设计中,只有Kafka Controller控制器在ZooKeeper上注册分区和副本相应的监听器,其他的broker极少需要再监听ZooKeeper中的数据变化。

不过每个broker还是会对ZooKeeper中/controller节点添加监听器,以此来监听(ControllerChangeHandler)控制器是否换人了。

当/controller节点的数据发生变化时,每个broker都会更新自身内存中保存的activeControllerId。

6.运行中broker的控制器选举

当运行中的控制器突然宕机或意外终止时,Kafka 能够快速地感知到,并立即启用备用控制器来代替之前失败的控制器。

这个过程就被称为 Failover,该过程是自动完成的,无需手动干预。

kafka常用客户端依赖 kafka 服务端_kafka常用客户端依赖_07

三、服务端重要配置参数及其含义

broker.id 每个broker的id需要不一样,是Broker的唯一标识

bootstrap.servers 用来发现kafka集群元数据信息的服务地址

参数名称

默认值

参数含义

auto.create.topics.enable

true

是否开启自动创建主题的功能

auto.leader.rebalance.enable

true

是否开启自动leader再均衡的功能

background.threads

10

指定执行后台任务的线程数

compression.type

producer

消息的压缩类型。Kafka支持的压缩类型有Gzip、Snappy、LZ4等。默认值是"producer"表示根据生产者使用的压缩类型压缩。

uncompressed表示不启用压缩。

delete.topic.enable

true

是否可以删除主题。

leader.imbalance.check.interval.seconds

300

检查leader是否分布不均匀的周期。

leader.imbalance.per.broker.percentage

10

允许leader不均衡的比例,如果超过这个值就会触发Leader再平衡操作(前提是auto.leader.rebalance.enable为true)

log.flush.interval.messages

Long.MAX_VALUE

如果日志文件中的消息在存入磁盘前的数量达到这个参数所配置的阈值,则会强制将这些刷新日志文件到磁盘中。

消息在写入磁盘前还要经历一层操作系统页缓存,如果期间发生掉电,则这些页缓存中的数据会丢失,调小这个参数的大小会增加消息的可靠性,但也会降低系统的整体性能

log.flush.interval.ms

null

刷新日志文件的时间间隔。如果没有配置这个值,则会依据log.flush.scheduler.interval.ms参数设置的值来运作。

log.flush.scheduler.interval.ms

Long.MAX_VALUE

检查日志文件是否需要刷新的时间间隔

log.retention.bytes

-1

日志文件的最大保留大小(分区级别,注意与log.segment.bytes的区别)

log.retention.hours

168(7天)

日志文件的留存时间,单位为小时

log.retention.minutes

null

日志文件的留存时间,单位为分钟

log.retention.ms

null

日志文件的留存时间,单位为毫秒

log.retention.hours

log.retention.minutes

log.retention.ms

这三个参数中,log.retention.ms的优先级最高

log.roll.hours

168(7天)

经过多长时间之后会强制新建一个日志分段。默认7天

log.roll.ms

null

同上,不过单位为毫秒

log.segment.bytes

1073741824(1GB)

日志分段文件的最大值,超过这个值会强制创建一个新的日志分段

log.segment.delete.delay.ms

60000(60秒)

从操作系统删除文件前的等待时间

min.insync.replicas

1

ISR集合中最少的副本数

num.io.threads

8

处理请求的线程数,包含磁盘I/0

num.network.threads

3

处理接收和返回响应的线程数

log.cleaner.enable

true

是否开启日志清理的功能

log.cleaner.min.cleanable.ratio

0.5

限定可执行清理操作的最小污浊率

log.cleanup.policy

delete

日志清理策略,还有一个可选项为compact,表示日志压缩

log.index.interval.bytes

4096

每隔多少个字节的消息量写入就添加一条索引

log.index.size.max.bytes

10485760(10MB)

索引文件的最大值

log.message.format.version

2.0-IV1

消息格式的版本

log.message.timestamp.type

CreateTime

消息中的时间戳类型,另一个可选项为LogAppendTime

CreateTime表示消息创建的时间

LogAppendTime表示消息追加到日志中的时间

log.retention.check.interval.ms

300000(5分钟)

日志清理的检查周期

num.partitions

1

主题中默认的分区数

reserved.broker.max.id

1000

broker.id能配置的最大值,同时reserved.broker.max.id+1也是自动创建broker.id值的起始大小

create.topic.policy.class.name

null

创建主题时用来验证合法性的策略,这个参数配置的是一个类的全限定名,需要实现org.apache.kafka.server.policy.CreateTopicPolicy接口

broker.id.generation.enable

true

是否开启自动生成broker.id的功能

broker.rack

null

配置broker的机架信息