三、Kafka的高可用

目录

 

一、高可用的由来

1.1 为何需要Replication

二、Kafka HA设计解析

2.1 如何将所有Replica均匀分布到整个集群

2.2 Data Replication(副本策略)

2.2.1 消息传递同步策略

2.2.2 ACK前需要保证有多少个备份

2.2.4 如何处理所有Replica都不工作

2.2.5 选举Leader

三、HA相关ZooKeeper结构

3.1 admin

3.2 broker

3.3 controller

四、producer发布消息

4.1 写入方式

4.2 消息路由

4.3 写入流程

5.1 存储方式

5.2 存储策略

六、Topic的创建和删除

6.1 创建topic

6.2 删除topic

七、broker failover(故障切换)

八、controller failover


一、高可用的由来

 

1.1 为何需要Replication

  在kafka中,每个主题可以有多个分区,每个分区又可以有多个副本。这多个副本中,只有一个是leader,而其他的都是follower副本。仅有leader副本可以对外提供服务。

多个follower副本通常存放在和leader副本不同的broker中。通过这样的机制实现了高可用,当某台机器挂掉后,其他follower副本也能迅速”转正“,开始对外提供服务。

 

 

二、Kafka HA设计解析

 

2.1 如何将所有Replica均匀分布到整个集群

 

为了更好的做负载均衡,Kafka尽量将所有的Partition均匀分配到整个集群上。一个典型的部署方式是一个Topic的Partition数量大于Broker的数量。同时为了提高Kafka的容错能力,也需要将同一个Partition的Replica尽量分散到不同的机器。实际上,如果所有的Replica都在同一个Broker上,那一旦该Broker宕机,该Partition的所有Replica都无法工作,也就达不到HA的效果。同时,如果某个Broker宕机了,需要保证它上面的负载可以被均匀的分配到其它幸存的所有Broker上。

Kafka分配Replica的算法如下:

  • 将所有Broker(假设共n个Broker)和待分配的Partition排序
  • 将第i个Partition分配到第(i mod n)个Broker上
  • 将第i个Partition的第j个Replica分配到第((i + j) mode n)个Broker上

 

2.2 Data Replication(副本策略)

 

Kafka的高可靠性的保障来源于其健壮的副本(replication)策略。

 

2.2.1 消息传递同步策略

 

Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader,然后无论该Topic的Replication Factor为多少,Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每个Follower都从Leader pull数据。这种方式上,Follower存储的数据顺序与Leader保持一致。Follower在收到该消息并写入其Log后,向Leader发送ACK。一旦Leader收到了ISR中的所有Replica的ACK,该消息就被认为已经commit了,Leader将增加HW并且向Producer发送ACK。

为了提高性能,每个Follower在接收到数据后就立马向Leader发送ACK,而非等到数据写入Log中。因此,对于已经commit的消息,Kafka只能保证它被存于多个Replica的内存中,而不能保证它们被持久化到磁盘中,也就不能完全保证异常发生后该条消息一定能被Consumer消费。

Consumer读消息也是从Leader读取,只有被commit过的消息才会暴露给Consumer。

Kafka Replication的数据流如下图所示:

 

三、Kafka的高可用_第1张图片

 

2.2.2 ACK前需要保证有多少个备份

 

 

对于Kafka而言,定义一个Broker是否“活着”包含两个条件:

  • 一是它必须维护与ZooKeeper的session(这个通过ZooKeeper的Heartbeat机制来实现)。
  • 二是Follower必须能够及时将Leader的消息复制过来,不能“落后太多”。

 

2.2.4 如何处理所有Replica都不工作

 

在ISR中至少有一个follower时,Kafka可以确保已经commit的数据不丢失,但如果某个Partition的所有Replica都宕机了,就无法保证数据不丢失了。这种情况下有两种可行的方案:

 

  • 等待ISR中的任一个Replica“活”过来,并且选它作为Leader
  • 选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader

 

这就需要在可用性和一致性当中作出一个简单的折衷。如果一定要等待ISR中的Replica“活”过来,那不可用的时间就可能会相对较长。而且如果ISR中的所有Replica都无法“活”过来了,或者数据都丢失了,这个Partition将永远不可用。选择第一个“活”过来的Replica作为Leader,而这个Replica不是ISR中的Replica,那即使它并不保证已经包含了所有已commit的消息,它也会成为Leader而作为consumer的数据源(前文有说明,所有读写都由Leader完成)。Kafka0.8.*使用了第二种方式。根据Kafka的文档,在以后的版本中,Kafka支持用户通过配置选择这两种方式中的一种,从而根据不同的使用场景选择高可用性还是强一致性。

 

2.2.5 选举Leader

 

最简单最直观的方案是,所有Follower都在ZooKeeper上设置一个Watch,一旦Leader宕机,其对应的ephemeral znode会自动删除,此时所有Follower都尝试创建该节点,而创建成功者(ZooKeeper保证只有一个能创建成功)即是新的Leader,其它Replica即为Follower。

但是该方法会有3个问题:

1.split-brain 这是由ZooKeeper的特性引起的,虽然ZooKeeper能保证所有Watch按顺序触发,但并不能保证同一时刻所有Replica“看”到的状态是一样的,这就可能造成不同Replica的响应不一致

2.herd effect (羊群效应)如果宕机的那个Broker上的Partition比较多,会造成多个Watch被触发,造成集群内大量的调整

3.ZooKeeper负载过重 每个Replica都要为此在ZooKeeper上注册一个Watch,当集群规模增加到几千个Partition时ZooKeeper负载会过重。

解决方式:

Kafka 0.8.*的Leader Election方案解决了上述问题,它在所有broker中选出一个controller,所有Partition的Leader选举都由controller决定。controller会将Leader的改变直接通过RPC的方式(比ZooKeeper Queue的方式更高效)通知需为为此作为响应的Broker。同时controller也负责增删Topic以及Replica的重新分配。

三、HA相关ZooKeeper结构

三、Kafka的高可用_第2张图片

3.1 admin

该目录下znode只有在有相关操作时才会存在,操作结束时会将其删除

/admin/reassign_partitions用于将一些Partition分配到不同的broker集合上。对于每个待重新分配的Partition,Kafka会在该znode上存储其所有的Replica和相应的Broker id。该znode由管理进程创建并且一旦重新分配成功它将会被自动移除。

3.2 broker

即/brokers/ids/[brokerId])存储“活着”的broker信息。

topic注册信息(/brokers/topics/[topic]),存储该topic的所有partition的所有replica所在的broker id,第一个replica即为preferred replica,对一个给定的partition,它在同一个broker上最多只有一个replica,因此broker id可作为replica id。

3.3 controller

/controller -> int (broker id of the controller)存储当前controller的信息

/controller_epoch -> int (epoch)直接以整数形式存储controller epoch,而非像其它znode一样以JSON字符串形式存储。

四、producer发布消息

 

4.1 写入方式

producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 patition 中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障 kafka 吞吐率)。

4.2 消息路由

producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。其路由机制为:

1、 指定了 patition,则直接使用;
2、 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition
3、 patition 和 key 都未指定,使用轮询选出一个 patition。

 

4.3 写入流程

 

producer 写入消息序列图如下所示:

三、Kafka的高可用_第3张图片

流程说明:

1、 producer 先从 zookeeper 的 "/brokers/.../state" 节点找到该 partition 的 leader 
2、 producer 将消息发送给该 leader 
3、 leader 将消息写入本地 log 
4、 followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK 
5、 leader 收到所有 ISR 中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK

 

5.1 存储方式

 

物理上把 topic 分成一个或多个 patition(对应 server.properties 中的 num.partitions=3 配置),每个 patition 物理上对应一个文件夹(该文件夹存储该 patition 的所有消息和索引文件),如下:

三、Kafka的高可用_第4张图片

 

5.2 存储策略

基于时间:log.retention.hours=168 
基于大小:log.retention.bytes=1073741824

 

六、Topic的创建和删除

 

6.1 创建topic

 

创建 topic 的序列图如下所示:

三、Kafka的高可用_第5张图片

流程说明:

  1.  controller 在 ZooKeeper 的 /brokers/topics 节点上注册 watcher,当 topic 被创建,则 controller 会通过 watch 得到该 topic 的 partition/replica 分配。
  2. ontroller从 /brokers/ids 读取当前所有可用的 broker 列表,对于 set_p 中的每一个 partition: 从分配给该 partition 的所有 replica(称为AR)中任选一个可用的 broker 作为新的 leader,并将AR设置为新的 ISR 将新的 leader 和 ISR 写入 /brokers/topics/[topic]/partitions/[partition]/state
  3. controller 通过 RPC 向相关的 broker 发送 LeaderAndISRRequest。

6.2 删除topic

 

删除 topic 的序列图如下所示:

三、Kafka的高可用_第6张图片

流程说明:

1、 controller 在 zooKeeper 的 /brokers/topics 节点上注册 watcher,当 topic 被删除,则 controller 会通过 watch 得到该 topic 的 partition/replica 分配。

2、 若 delete.topic.enable=false,结束;否则 controller 注册在 /admin/delete_topics 上的 watch 被 fire,controller 通过回调向对应的 broker 发送 StopReplicaRequest。

 

七、broker failover(故障切换)

 

kafka broker failover 序列图如下所示:

三、Kafka的高可用_第7张图片

流程说明:

1、 controller 在 zookeeper 的 /brokers/ids/[brokerId] 节点注册 Watcher,当 broker 宕机时 zookeeper 会 fire watch

2、 controller 从 /brokers/ids 节点读取可用broker

3、 controller决定set_p,该集合包含宕机 broker 上的所有 partition

4、 对 set_p 中的每一个 partition

  •     从/brokers/topics/[topic]/partitions/[partition]/state 节点读取 ISR
  •    决定新 leader
  •   将新 leader、ISR、controller_epoch 和 leader_epoch 等信息写入 state 节点

 5、 通过 RPC 向相关 broker 发送 leaderAndISRRequest 命令

 

八、controller failover

 

当 controller 宕机时会触发 controller failover。每个 broker 都会在 zookeeper 的 "/controller" 节点注册 watcher,当 controller 宕机时 zookeeper 中的临时节点消失,所有存活的 broker 收到 fire 的通知,每个 broker 都尝试创建新的 controller path,只有一个竞选成功并当选为 controller。

当新的 controller 当选时,会触发 KafkaController.onControllerFailover 方法,在该方法中完成如下操作:

1、 读取并增加 Controller Epoch。

2、 在 reassignedPartitions Patch(/admin/reassign_partitions) 上注册 watcher。

3、 在 preferredReplicaElection Path(/admin/preferred_replica_election) 上注册 watcher。

4、 通过 partitionStateMachine 在 broker Topics Patch(/brokers/topics) 上注册 watcher。

5、 若 delete.topic.enable=true(默认值是 false),则 partitionStateMachine 在 Delete Topic Patch(/admin/delete_topics) 上注册 watcher。

6、 通过 replicaStateMachine在 Broker Ids Patch(/brokers/ids)上注册Watch。

7、 初始化 ControllerContext 对象,设置当前所有 topic,“活”着的 broker 列表,所有 partition 的 leader 及 ISR等。

8、 启动 replicaStateMachine 和 partitionStateMachine。

9、 将 brokerState 状态设置为 RunningAsController。

10、 将每个 partition 的 Leadership 信息发送给所有“活”着的 broker。

11、 若 auto.leader.rebalance.enable=true(默认值是true),则启动 partition-rebalance 线程。 12、 若 delete.topic.enable=true 且Delete Topic Patch(/admin/delete_topics)中有值,则删除相应的Topic。

 

你可能感兴趣的:(中间件,kafka)