kafka如何做到高可用的

常常想如果让你去设计一个高可用的系统,你怎么去做?这里要回答两个问题:

  1. 如何保证宕机的时候数据不丢失? 答:副本
  2. 多副本之间数据如何同步? 答:同步;异步;半同步;ISR

这里我们看一下kafka是怎么设计做到高可用的,学习一下它:

如何保证宕机的时候数据不丢失?

对于每一个Topic,我们都可以设置它包含几个Partition,每个Partition负责存储这个Topic一部分的数据。然后Kafka的Broker集群中,每台机器上都存储了一些Partition,也就存放了Topic的一部分数据,这样就实现了Topic的数据分布式存储在一个 Broker 集群上。但是有一个问题,万一一个 Kafka Broker宕机了,此时上面存储的数据不就丢失了吗?没错,这就是一个比较大的问题了,分布式系统的数据丢失问题,是它首先必须要解决的,一旦说任何一台机器宕机,此时就会导致数据的丢失。

所以如果大家去分析任何一个分布式系统的原理,比如说 Zookeeper、Kafka、RedisCluster、Elasticsearch、HDFS,等等。其实它们都有自己内部的一套多副本冗余的机制,多副本冗余几乎是现在任何一个优秀的分布式系统都一般要具备的功能。在Kafka集群中,每个Partition都有多个副本,其中一个副本叫做Leader,其他的副本叫做Follower。

假设一个Topic拆分为了3个Partition,分别是 Partition0,Partiton1,Partition2,此时每个Partition都有2个副本。比如 Partition0 有一个副本是 Leader,另外一个副本是Follower,Leader和Follower两个副本是分布在不同机器上的。这样的多副本冗余机制,可以保证任何一台机器挂掉,都不会导致数据彻底丢失,因为起码还是有副本在别的机器上的。

为了尽量做好负载均衡和容错能力,需要将同一个Partition的Replica尽量分散到不同的机器。如果所有的Replica都在同一个Broker上,那一旦该Broker宕机,该Partition的所有Replica都无法工作,那么这些Replica也就失去的意义。

多副本(replication)之间数据如何同步?

在看具体怎么同步之前我们先需要了解几个概念:

  • 我们创建topic的时候可以指定–replication-factor 3,表示分区的副本数(一般根据broker数量决定)
  • 我们说过leader是负责读写的节点,为了保证较高的处理效率,消息的读写都是在固定的一个副本上完成。这个副本就是Leader,而其他副本则是Follower。无论副本有多少,Producer都只把消息发送到Leader上,Follower则会定期地到Leader上Pull数据。
  • ISR是leader负责维护的,与其保持同步的Replica列表,即当前活跃的副本列表。如果一个Follow落后太多,Leader会将它从ISR中移除,落后太多意思是该Follow复制的消息落后于Leader的条数超过预定值(参数:replica.lag.max.messages 默认值:4000)或者Follow长时间没有向leader发送fetch请求(参数:replica.lag.time.max.ms 默认值:10000)
  • 为了保证可靠性,我们通常设置ACK=all。Follower收到消息后,会像Leader发送ACK。一旦Leader收到了ISR中所有Replica的ACK,leader就commit,那么Leader就像Producer发送ACK。

这些概念是高可用的基础。

接着我们再看多个副本之间数据是如何同步的?其实任何一个 Partition,只有Leader是对外提供读写服务的。也就是说,如果有一个客户端往一个 Partition 写入数据,此时一般就是写入这个Partition的Leader副本。然后Leader副本接收到数据之后,Follower副本会不停的给它发送请求尝试去拉取Leader的数据,拉取到自己本地后,写入磁盘中。

一般来说,对于这种情况有两个处理方法

  • 同步复制,当producer向所有的Replica写入成功消息后才返回。一致性得到保障,但是延迟太高,吞吐率降低。
  • 异步复制,所有的Replica选取一个一个leader,producer向leader写入成功即返回,leader负责将消息同步给其他的所有Replica。但是消息同步一致性得不到保证,但是保证了快速的响应。

而kafka选取了一个折中的方式:ISR(in-sync replicas)。producer每次发送消息,将消息发送给leader,leader将消息同步给他“信任”的“小弟们”就算成功,巧妙的均衡了确保数据不丢失以及吞吐率。在所有的Replica中,leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护。如果一个replica落后leader太多,leader会将其剔除。如果另外的replica跟上脚步,leader会将其加入。

  • 同步:leader向ISR中的所有replica同步消息,当收到所有ISR中replica的ack之后,leader才commit。
  • 异步:收到同步消息的ISR中的replica,异步将消息同步给ISR集合外的replica。

如何Leader的选举?

了解了Kafka如何做Replication,随之而来的疑问便是如何选取leader?leader选举可谓是一个经典问题,立马想到了paxos,raft、Zab等算法,然而kafka采用的方法相比就简单很多:Kafka的Leader选举是通过在zookeeper上创建/controller临时节点来实现leader选举,并在该节点中写入当前broker的元信息。一个节点只能被一个客户端创建成功,创建成功的broker即为leader,即先到先得原则。当leader和zookeeper失去连接时,临时节点会删除,而其他broker会监听该节点的变化,当节点删除时,其他broker会收到事件通知,重新发起leader选举。另外两种方式对比

1.基于Zookeeper的选举方式

大数据很多组件都有Leader选举的概念,如HBASE等。它们大都基于ZK进行选举,所有Follow都在ZK上面注册一个Watch,一旦Leader宕机,Leader对于的Znode会自动删除,那些Follow由于在Leader节点上注册了Watcher,故可以得到通知,就去参与下一轮选举,尝试去创建该节点,zK会保证只有一个Follow创建成功,成为新的Leader。
但是这种方式有几个缺点:

  • split-brain:这是由ZooKeeper的特性引起的,虽然ZooKeeper能保证所有Watch按顺序触发,但并不能保证同一时刻所有Replica“看”到的状态是一样的,这就可能造成不同Replica的响应不一致
  • herd effect 如果宕机的那个Broker上的Partition比较多,会造成多个Watch被触发,造成集群内大量的调整
  • ZooKeeper负载过重 每个Replica都要为此在ZooKeeper上注册一个Watch,当集群规模增加到几千个Partition时ZooKeeper负载会过重。

2. 基于Controller的选举方式

Kafka 0.8后的Leader Election方案解决了上述问题,它在所有broker中选出一个controller,所有Partition的Leader选举都由controller决定。controller会将Leader的改变直接通过RPC的方式(比ZooKeeper Queue的方式更高效)通知需为为此作为响应的Broker。同时controller也负责增删Topic以及Replica的重新分配。
优点:极大缓解了HerdEffect问题、减轻了ZK的负载,Controller与Leader\Follower之间通过RPC通信,高效且实时。缺点:引入Controller增加了复杂度,且需要考虑Controller的Failover

如果Replica都死了怎么办?

我们知道,只要至少有一个replica,就能保证数据不丢失,可是如果某个partition的所有replica都死了怎么办?有两种方案:

  • 等待在ISR中的副本起死回生并选择该副本作为leader
  • 选择第一个活过来的副本 (不一定在 ISR中),作为leader。

这就需要在可用性和一致性当中做个折衷。如果一定要等待副本起死回生,那么等待的时间可能比较长,而且如果都没办法活过来,那么pertition将永远不可用,这样降低了可用性。如果是第二种,可能不能保证包含所有已经commit的消息,因此会造成数据丢失,但保证了可用性。目前KAFKA选用的是第二种方式,支持选择不能保证一致的副本。你也可以通过参数unclean.leader.election.enable禁用它。

Broker宕机怎么办?

Controller在Zookeeper的/brokers/ids节点上注册Watch。一旦有Broker宕机(本文用宕机代表任何让Kafka认为其Broker die的情景,包括但不限于机器断电,网络不可用,GC导致的Stop The World,进程crash等),其在Zookeeper对应的Znode会自动被删除,Zookeeper会fire Controller注册的Watch,Controller即可获取最新的幸存的Broker列表。

Controller决定set_p,该集合包含了宕机的所有Broker上的所有Partition。
对set_p中的每一个Partition:

  • 从/brokers/topics/[topic]/partitions/[partition]/state读取该Partition当前的ISR。
  • 决定该Partition的新Leader。如果当前ISR中有至少一个Replica还幸存,则选择其中一个作为新Leader,新的ISR则包含当前ISR中所有幸存的Replica。否则选择该Partition中任意一个幸存的Replica作为新的Leader以及ISR(该场景下可能会有潜在的数据丢失)。如果该Partition的所有Replica都宕机了,则将新的Leader设置为-1。
  • 将新的Leader,ISR和新的leader_epoch及controller_epoch写入/brokers/topics/[topic]/partitions/[partition]/state。
    [zk: localhost:2181(CONNECTED) 13] get /brokers/topics/bdstar/partitions/0/state
    {“controller_epoch”:1272,“leader”:0,“version”:1,“leader_epoch”:4,“isr”:[0,2]}

直接通过RPC向set_p相关的Broker发送LeaderAndISRRequest命令。Controller可以在一个RPC操作中发送多个命令从而提高效率。

Controller宕机怎么办?

每个Broker都会在/controller上注册一个Watch。当前Controller宕机时(此处我kill了id=1的broker进程),对应的/controller会自动消失。所有“活”着的Broker都会去竞选成为新的Controller,会创建新的Controller Path。注意:只会有一个竞选成功(这点由Zookeeper保证)。竞选成功者即为新的Leader,竞选失败者则重新在新的Controller Path上注册Watch。因为Zookeeper的Watch是一次性的,被fire一次之后即失效,所以需要重新注册。

参考地址:

http://bigdata-star.com/archives/1510

http://developer.51cto.com/art/201904/595538.htm

http://www.jasongj.com/2015/04/24/KafkaColumn2/

https://blog.csdn.net/C_J33/article/details/82730041

http://www.thinkyixia.com/2017/10/25/kafka-2/#kafka%E6%8B%93%E6%89%91%E7%BB%93%E6%9E%84

http://lxw1234.com/archives/2015/09/504.htm

发布了223 篇原创文章 · 获赞 308 · 访问量 84万+

猜你喜欢

转载自blog.csdn.net/maoyeqiu/article/details/102708588