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。

相关推荐
学习中的阿陈2 小时前
Hadoop伪分布式环境配置
大数据·hadoop·分布式
CesareCheung2 小时前
JMeter分布式压力测试
分布式·jmeter·压力测试
thginWalker3 小时前
面试鸭Java八股之Kafka
kafka
失散134 小时前
分布式专题——10.5 ShardingSphere的CosID主键生成框架
java·分布式·架构·分库分表·shadingsphere
winfield8215 小时前
Kafka 线上问题排查完整手册
kafka
Cxzzzzzzzzzz8 小时前
RabbitMQ 在实际开发中的应用场景与实现方案
分布式·rabbitmq
在未来等你8 小时前
Kafka面试精讲 Day 16:生产者性能优化策略
大数据·分布式·面试·kafka·消息队列
王大帅の王同学8 小时前
Thinkphp6接入讯飞星火大模型Spark Lite完全免费的API
大数据·分布式·spark
一氧化二氢.h10 小时前
通俗解释redis高级:redis持久化(RDB持久化、AOF持久化)、redis主从、redis哨兵、redis分片集群
redis·分布式·缓存
爱睡觉的圈圈14 小时前
分布式IP代理集群架构与智能调度系统
分布式·tcp/ip·架构