Kafka服务端的各种机制实现原理

文章目录

zookeeper整体元数据

Zookeeper客户端工具: prettyZoo下载地址

我们知道kafka集群选举相关都是基于zookeeper来实现的,kafka注册进zookeeper中的数据目录如下图所示

上图中,最外层的一个方框都是kafka注册的元数据,如果想要kafka的这些元数据在一个统一的一级目录下,那么我们就可以在kafka的conf/server.properties中,指定zookeeper集群地址时,在每个ip地址后面统一加一个目录

在那么多的元数据中,比较重要的数据就是brokers目录和controller。其中brokers目录下记录了当前kafka集群下的所有broker存活节点;还有所有的topic信息,某个topic下partition分区的情况 Leader是在哪个Broker节点上,整个partition分区的复制集都在哪些节点上

而controller就是记录了kafka集群的多个Broker中,哪一个Broker充当controller角色

Controller信息如下

Kafka集群中的每个Broker启动后,都会往Zookeeper注册一个临时节点/brokers/ids/{broker.id}。如果Kafka的服务器非正常关机,这时Zookeeper上的临时节点不会注销,Kafka服务重启后,就有可能无法注册这个临时节点而报错

Controller Broker选举机制

Kafka的集群需要选举一个Controller Broker角色来管理整个集群的Broker和partition分区相关的信息,并负责将这些信息同步给集群内其他Broker。

Kafka集群下的Broker启动时,都会去往Zookeeper中创建一个Controller的临时节点,注册信息如下图右下角所示。Zookeeper保证只有一个Broker能创建成功。

创建controller临时节点的 Broker需要与Zookeeper保存长连接,定时发送心跳;允许其他Broker监听controller临时节点状态。

如果zookeeper长时间收不到当前Controller Broker的心跳,就会删除这个临时节点,同时会向该临时节点对应的所有监听器广播节点变化事件。其他Broker就有一次开始尝试创建Controller临时节点。

Controller Broker主要就是监听zookeeper下的/brokers/ids/brokers/topics这些节点,感知对应的增减变化,并同步给集群下的其他Broker。

Leader Partition选举机制

一个Topic下可以存在多个partition分区,而每个partition分区由可以通过添加备份组成一个复制集,一个Partition复制集中会选举出一个Leader多个Follower,LeaderPartition负责接收Client的读写请求,并将消息优先保存,再通知其他FollowerPartition来同步消息

专有名词概念:

  • AR: Assigned Repllicas。

    表示Kafka分区中副本集下的所有副本,包括不存活的partition。也就是下方命令中的Replicas这一列

  • ISR

    表示当前副本集中还存活的partition,能保持和Leader同步的Follower集合。如果Follower长时间没有向Leader发送通信请求(超时时间由replica.lag.time.max.ms参数设定,默认30S,那么这个Follower就会被移出ISR中。

  • OSR

    保存移出ISR的partition

我们在创建Topic时,可以通过--partitions来指定分区数,通过--replication-factor 来指定一个复制集下存在的partition数量。

bash 复制代码
# 创建一个hsTopic   其中共有4个partition分区,  每个分区下的复制集个数是3也就是一个Leader 两个Follower
[root@worker1 ~]# kafka-topics.sh --create --topic hsTopic --partitions 4 --replication-factor 3 --bootstrap-server localhost:9092
Created topic hsTopic.

# 查看所有的topic列表,就能在最后看到我们上方创建的hsTopic
[root@worker1 ~]# kafka-topics.sh --list --bootstrap-server localhost:9092
__consumer_offsets
__transaction_state
disTopic
hsTopic

# 通过使用--describe参数 查看某个topic的详细信息
[root@worker1 ~]# kafka-topics.sh --describe --topic hsTopic --bootstrap-server localhost:9092
Topic: hsTopic	TopicId: OZC7rONtR-ShGjwuq0xGpA	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: hsTopic	Partition: 0	Leader: 3	Replicas: 3,1,2	Isr: 3,1,2
	Topic: hsTopic	Partition: 1	Leader: 1	Replicas: 1,2,3	Isr: 1,2,3
	Topic: hsTopic	Partition: 2	Leader: 2	Replicas: 2,3,1	Isr: 2,3,1
	Topic: hsTopic	Partition: 3	Leader: 3	Replicas: 3,2,1	Isr: 3,2,1

# 其中最前面的一列表示分区号,Leader表示当前分区复制集的LeaderPartition所在的BrokerId, 
# Replicas表示复制集所在的brokerid,Isr表示当前复制集下还存活的Partition所在的brokerId

我们再去查看Zookeeper中的数据,下图中的数据,和我们上方命令中查询出来是也是一一对应的

Kafka的Leader Partition选举机制非常高效,直接使用的是Replicas这个集合中的第一个作为的Leader,在选举Leader Partition时,会按照AR的排名顺序,靠前的优先选举,只要这个靠前的Partition还在ISO中(需要存活),那么这个Partition就会被选举成Leader Partition。

测试,如下Partition2所示,它当前的Leader在broker2上,如果我此时把Broker2上的Kafka服务停止,我们来看看它的Leader 是否是选举到Broker3上去了

bash 复制代码
[root@worker1 ~]# kafka-topics.sh --describe --topic hsTopic --bootstrap-server localhost:9092
Topic: hsTopic	TopicId: OZC7rONtR-ShGjwuq0xGpA	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: hsTopic	Partition: 0	Leader: 3	Replicas: 3,1,2	Isr: 3,1,2
	Topic: hsTopic	Partition: 1	Leader: 1	Replicas: 1,2,3	Isr: 1,2,3
	Topic: hsTopic	Partition: 2	Leader: 2	Replicas: 2,3,1	Isr: 2,3,1
	Topic: hsTopic	Partition: 3	Leader: 3	Replicas: 3,2,1	Isr: 3,2,1
bash 复制代码
# 停止Broker2上的服务
[root@worker2 ~]# kafka-server-stop.sh 
[root@worker2 ~]# jps
3313 Jps
1496 QuorumPeerMain

Zookeeper上的数据也同步更新了,没有了broker.id = 2 的节点了

我们再来查看topic的信息,可以发现partition2的Leader果然是重新选举到broker.id=3的服务器上去了,并且Isr这一列也没有了broker.id =2的信息了

bash 复制代码
[root@worker1 ~]# kafka-topics.sh --describe --topic hsTopic --bootstrap-server localhost:9092
Topic: hsTopic	TopicId: OZC7rONtR-ShGjwuq0xGpA	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: hsTopic	Partition: 0	Leader: 3	Replicas: 3,1,2	Isr: 3,1
	Topic: hsTopic	Partition: 1	Leader: 1	Replicas: 1,2,3	Isr: 1,3
	Topic: hsTopic	Partition: 2	Leader: 3	Replicas: 2,3,1	Isr: 3,1
	Topic: hsTopic	Partition: 3	Leader: 3	Replicas: 3,2,1	Isr: 3,1

Leader Partition自动平衡机制

Leader Partition比较繁忙,既要处理Client的请求,又要处理复制集的数据同步,Kafka会尽力将Leader Partition分布在不同的Broker上。

因为不可抗力因素导致Leader Partition重新选举之后,就会出现多个Leader Partition在一个Broker上,这个Broker的压力就会明显高于其他Broker。

Kafka存在一个Leader Partition自动平衡机制:Kafka认为AR中的第一个节点就应该是Leader,这是它认为最理想的情况。Controller Broker会每隔leader.imbalance.check.interval.seconds时间,检查所有的Broker,如果发现某个Broker中Leader Partition的不理想比例达到了leader.imbalance.per.broker.percentage 阀值,就会触发一次自动平衡。

这个机制涉及到conf/server.properties配置文件中的几个重要参数:

官网 服务端的参数说明

properties 复制代码
# 注意要修改集群中所有broker的文件,并且要重启Kafka服务才能生效。
#1 自平衡开关。默认true,值的类型是boolean
auto.leader.rebalance.enable
Type:	boolean
Default:	true

#2 自平衡扫描间隔
leader.imbalance.check.interval.seconds
Type:	long
Default:	300

#3 自平衡触发比例
leader.imbalance.per.broker.percentage
Type:	int
Default:	10

也可以通过手动通过kafka-leader-election.sh脚本触发一次自平衡机制

接着Leader Partition选举机制的案例,此时我重启了broker.id=2的服务,此时各个LeaderPartition的分布情况如下所示

bash 复制代码
# 其中的partition2 因为触发了重选选举,导致了此时的Leaderpartition并不是在理想的节点Broker2上
[root@worker1 ~]# kafka-topics.sh --describe --topic hsTopic --bootstrap-server localhost:9092
Topic: hsTopic	TopicId: OZC7rONtR-ShGjwuq0xGpA	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: hsTopic	Partition: 0	Leader: 3	Replicas: 3,1,2	Isr: 3,1,2
	Topic: hsTopic	Partition: 1	Leader: 1	Replicas: 1,2,3	Isr: 1,3,2
	Topic: hsTopic	Partition: 2	Leader: 3	Replicas: 2,3,1	Isr: 3,1,2
	Topic: hsTopic	Partition: 3	Leader: 3	Replicas: 3,2,1	Isr: 3,1,2
	
	
# 手动触发所有Topic的Leader Partition自平衡 
# --election-type 选举的类型选择preferred,表示按照最理想的方式选leader  ,之后指定哪一个topic下的哪一个partition要进行自平衡
[root@worker1 ~]# kafka-leader-election.sh --election-type preferred --topic hsTopic --partition 2 --bootstrap-server localhost:9092
Successfully completed leader election (PREFERRED) for partitions hsTopic-2

# 此时 partition2的leader就成了broker2节点上的了,变为了最理想的情况
[root@worker1 ~]# kafka-topics.sh --describe --topic hsTopic --bootstrap-server localhost:9092
Topic: hsTopic	TopicId: OZC7rONtR-ShGjwuq0xGpA	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: hsTopic	Partition: 0	Leader: 3	Replicas: 3,1,2	Isr: 3,1,2
	Topic: hsTopic	Partition: 1	Leader: 1	Replicas: 1,2,3	Isr: 1,3,2
	Topic: hsTopic	Partition: 2	Leader: 2	Replicas: 2,3,1	Isr: 3,1,2
	Topic: hsTopic	Partition: 3	Leader: 3	Replicas: 3,2,1	Isr: 3,1,2

注意:

在生产环境下不建议使用自平衡,因为这是一个比较耗时的操作,涉及到了大量消息的转移与同步,并且在这个过程中可能存在丢失消息的风险。

对于性能要求比较高的线上环境,会选择将参数auto.leader.rebalance.enable设置为false,关闭Kafka的Leader Partition自平衡操作,而用其他运维的方式,在业务不繁忙的时间段,手动进行Leader Partiton自平衡,尽量减少自平衡过程对业务的影响。

Partition故障恢复机制

Leader partition所在的broker服务停止后,Kafka就会触发partition重新选举,原来LeaderPartition中还未来得及同步的数据怎么办?

专有名词概念:

  • LEO(log end offset),每个partition的最后一个offset

    Leader Partition接收Client的请求将消息持久化后就会更新LEO,Follower Partition从Leader Partition每同步一次消息就更新一次LEO

  • HW(High Watermark),复制集下的最小的一个LEO

    Follower Partition向Leader Partition拉取消息,并上报LEO给leader,Leader Partition就会计算出HW,Follower下次拉取消息再更新本机的HW。HW之后的消息在复制集下的所有Partition都存在,所以可以认为这是安全的消息,允许消费者进行消费;而HW之前的消息为不安全消息,对消费者是不可见的。

也就是说,生产者新发送的消息并不会立刻被消费者消费,需要等到HW移动到该消息对应的offset之后消费者才会消费该消息。

Follower Partition出现故障的处理流程:

  • 当前故障Partition 移出ISR列表,其他Follower Partition正常同步消息
  • 当故障的Partition恢复后,不会立刻加入到ISR列表中,会读取到出现故障时本机保存的HW值,删除HW之前的消息,然后去往Leader同步消息
  • 当同步的消息也就是LEO达到当前复制集的HW后,加入到ISR列表中

Leader Partition出现故障的处理流程:

  • 从AR中,靠后一个重选选举出一个Leader Partition,此时消息可能还没有同步完成,新leader和LEO可能是小于老leader的LEO的。
  • 新leader也删除HW之前的消息
  • Kafka的消息只能以leader的备份为准,其他Follower partition将各自Log文件中高于HW的消息删除,并开始向新Leader partition开始同步消息
  • 当之前的Leader partition恢复之后,将作为Follower partition,先读取到本机的HW,将HW之前的消息全部删除。然后开始向当前Leader partition同步消息,当LEO达到复制集的HW时,加入到ISR列表中

在这个过程当中,Kafka注重的是保护多个副本之间的数据一致性。但是这样,消息的安全性就得不到保障。例如在上述示例中,原本Partition0中的4,5,6,7号消息就被丢失掉了。

在这个机制中,有一个很重要的前提,那就是各个broker中记录的HW是一致的,可是HW和LEO同样是一个分布式的值,怎么保证HW在多个Broker中是一致的

Leader将当前最新计算出的HW同步给其他Follower这一过程中也存在一个延时,某些Follower先拉取消息就得到了最新的HW,某些Follower还没有拉取消息。如果在这个过程中,Leader所在Broker节点宕机了,这不就造成了前一部分Follower是最新的刚同步的HW,而后一部分Follower还没有得到最新的HW,那此时该怎么办嘞?

HW一致性保障-Epoch更新机制

HW的值在一个partition复制集中并不是总是一致的。

不一致的产生的原因:

Follower partition先向loader partition拉取消息,才能向leader上报LEO值。leader得到所有Follower上报的LEO之后计算出HW。Follower 下次拉取消息才能一起拉取最最新的HW,再更新HW。Leader和Follower更新HW的值就存在一定的时间延迟,如果有多个Follower,他们之间保存的HW也存在一定时间的不统一。

此时如果服务一切正常,那么Leader还是会稳步的向前推进HW;但是当Leader partition发生了切换,所有的Follower partition都按照自己的HW进行数据恢复,就会出现数据不一致的情况。

因此,Kafka还设计了Epoch机制,来保证HW的一致性。

  • epoch是一个递增的版本号,当发生Leader partition切换时就会递增,之间低版本的epoch就会无效

  • 每个Leader在上任之初都会新增一个epoch。这个记录包含新增的epoch版本号和当前Leader partition最新写入的第一个消息的偏移量offset。例如(25,100),25表示epoch的版本号,100表示当前新Leader写入的第一条消息offset是100。

    Broker会将这个epoch数据保存到内存中,并且会持久化到本地一个leader-epoch-checkpoint文件当中。

  • 这个leader-epoch-checkpoint文件本来就会在整个副本集中同步。当出现了Leader partition切换,新Leader就会进行更新添加自己的epoch

  • 接下里其他Follower partition要更新数据时,就可以不再依靠自己本机的HW值判断拉取消息的起点,而可以根据这个最新的epoch条目来判断

这个关键的leader-epoch-checkpoint文件保存在Broker上每个partition对应的本地目录中。这是一个文本文件,可以直接查看。他的内容大概是这样样子的

bash 复制代码
[oper@worker1 disTopic-0]$ cat leader-epoch-checkpoint 
0
1
29 2485991681

其中第一行版本号,第二行表示下面的记录数。这两行数据没有太多的实际意义。

从第三行开始,可以看到两个数字。这两个数字就是epoch 和 offset。epoch就是表示leader的epoch版本。从0开始,当leader变更一次epoch就会+1。

offset则对应该epoch版本的leader写入第一条消息的offset。可以理解为用户可以消费到的最早的消息offset。

相关推荐
斑驳竹影4 小时前
【RabbitMQ】之高可用集群搭建
分布式·rabbitmq
floret*5 小时前
Kafka高频面试题详解
分布式·kafka
Wlq04155 小时前
分布式技术缓存技术
分布式·缓存
明达技术6 小时前
LVDS高速背板总线:打造分布式I/O高效数据传输新境界
分布式·物联网·自动化·lvds
java1234_小锋6 小时前
分布式环境下宕机的处理方案有哪些?
分布式
大数据魔法师6 小时前
Hadoop生态圈框架部署(六)- HBase完全分布式部署
hadoop·分布式·hbase
知否&知否7 小时前
kafka中是如何快速定位到一个offset的
分布式·kafka
꧁薄暮꧂8 小时前
Kafka生产者如何提高吞吐量?
分布式·kafka
墨水\\9 小时前
分布式----Ceph部署(上)
分布式·ceph
AllWe$10 小时前
分布式-锁
分布式·后端