Broker
副本机制
所谓的副本机制(Replication),也可以称之为备份机制,通常是指分布式系统在多台网络互联的机器上保存有相同的数据拷贝。副本机制有什么好处呢?
- 提供数据冗余。即使系统部分组件失效,系统依然能够继续运转,因而增加了整体可用性以及数据持久性。
- 提供高伸缩性。支持横向扩展,能够通过增加机器的方式来提升读性能,进而提高读操作吞吐量。
- 改善数据局部性。允许将数据放入与用户地理位置相近的地方,从而降低系统延时。
这些优点都是在分布式系统教科书中最常被提及的,但是有些遗憾的是,对于Apache Kafka而言,目前只能享受到副本机制带来的第1个好处,也就是提供数据冗余实现高可用性和高持久性。
副本定义
Kafka是有主题概念的,而每个主题又进一步划分成若干个分区。副本的概念实际上是在分区层级下定义的,每个分区配置有若干个副本。
所谓副本(Replica),本质就是一个只能追加写消息的提交日志。根据Kafka副本机制的定义,同一个分区下的所有副本保存有相同的消息序列,这些副本分散保存在不同的Broker上,从而能够对抗部分Broker宕机带来的数据不可用。
在实际生产环境中,每台Broker都可能保存有各个主题下不同分区的不同副本,因此,单个Broker上存有成百上千个副本的现象是非常正常的。
副本角色
Apache Kafka确保副本中所有的数据都是一致的,最常见的解决方案就是采用基于领导者(Leader-based)的副本机制。
第一,在Kafka中,副本分成两类:领导者副本(Leader Replica)和追随者副本(Follower Replica)。每个分区在创建时都要选举一个副本,称为领导者副本,其余的副本自动称为追随者副本。
第二,Kafka的副本机制比其他分布式系统要更严格一些。在Kafka中,追随者副本是不对外提供服务的。这就是说,任何一个追随者副本都不能响应消费者和生产者的读写请求。所有的请求都必须由领导者副本来处理,或者说,所有的读写请求都必须发往领导者副本所在的Broker,由该Broker负责处理。追随者副本不处理客户端请求,它唯一的任务就是从领导者副本异步拉取消息,并写入到自己的提交日志中,从而实现与领导者副本的同步。
第三,当领导者副本挂掉了,或者说领导者副本所在的Broker宕机时,Kafka依托于ZooKeeper提供的监控功能能够实时感知到,并立即开启新一轮的领导者选举,从追随者副本中选一个作为新的领导者。老Leader副本重启回来后,只能作为追随者副本加入到集群中。
副本机制有两个方面的好处
1.方便实现“Read-your-writes”。
所谓Read-your-writes,顾名思义就是,当你使用生产者API向Kafka成功写入消息后,马上使用消费者API去读取刚才生产的消息。
举个例子,比如你平时发微博时,你发完一条微博,肯定是希望能立即看到的,这就是典型的Read-your-writes场景。如果允许追随者副本对外提供服务,由于副本同步是异步的,因此有可能出现追随者副本还没有从领导者副本那里拉取到最新的消息,从而使得客户端看不到最新写入的消息。
2.方便实现单调读(Monotonic Reads)。
什么是单调读呢?就是对于一个消费者用户而言,在多次消费消息时,它不会看到某条消息一会儿存在一会儿不存在。
如果允许追随者副本提供读服务,那么假设当前有2个追随者副本F1和F2,它们异步地拉取领导者副本数据。倘若F1拉取了Leader的最新消息而F2还未及时拉取,那么,此时如果有一个消费者先从F1读取消息之后又从F2拉取消息,它可能会看到这样的现象:第一次消费时看到的最新消息在第二次消费时不见了,这就不是单调读一致性。但是,如果所有的读请求都是由Leader来处理,那么Kafka就很容易实现单调读一致性。
In-sync Replicas(ISR)
追随者副本不提供服务,只是定期地异步拉取领导者副本中的数据而已。既然是异步的,就存在着不可能与Leader实时同步的风险。在探讨如何正确应对这种风险之前,我们必须要精确地知道追随者副本到底在什么条件下才算与Leader同步。
基于这个想法,Kafka引入了In-sync Replicas,也就是所谓的ISR副本集合。ISR中的副本都是与Leader同步的副本,相反,不在ISR中的追随者副本就被认为是与Leader不同步的。
Leader副本天然就在ISR中。也就是说,ISR不只是追随者副本集合,它必然包括Leader副本。甚至在某些情况下,ISR只有Leader这一个副本。
这个标准就是Broker端参数replica.lag.time.max.ms参数值。这个参数的含义是Follower副本能够落后Leader副本的最长时间间隔,当前默认值是10秒。这就是说,只要一个Follower副本落后Leader副本的时间不连续超过10秒,那么Kafka就认为该Follower副本与Leader是同步的,即使此时Follower副本中保存的消息明显少于Leader副本中的消息。
Follower副本唯一的工作就是不断地从Leader副本拉取消息,然后写入到自己的提交日志中。如果这个同步过程的速度持续慢于Leader副本的消息写入速度,那么在replica.lag.time.max.ms时间后,此Follower副本就会被认为是与Leader副本不同步的,因此不能再放入ISR中。此时,Kafka会自动收缩ISR集合,将该副本“踢出”ISR。
值得注意的是,倘若该副本后面慢慢地追上了Leader的进度,那么它是能够重新被加回ISR的。这也表明,ISR是一个动态调整的集合,而非静态不变的。
Unclean领导者选举(Unclean Leader Election)
既然ISR是可以动态调整的,那么自然就可以出现这样的情形:ISR为空。因为Leader副本天然就在ISR中,如果ISR为空了,就说明Leader副本也“挂掉”了,Kafka需要重新选举一个新的Leader。可是ISR是空,此时该怎么选举新Leader呢?
Kafka把所有不在ISR中的存活副本都称为非同步副本。通常来说,非同步副本落后Leader太多,因此,如果选择这些副本作为新Leader,就可能出现数据的丢失。毕竟,这些副本中保存的消息远远落后于老Leader中的消息。在Kafka中,选举这种副本的过程称为Unclean领导者选举。Broker端参数unclean.leader.election.enable控制是否允许Unclean领导者选举。
开启Unclean领导者选举可能会造成数据丢失,但好处是,它使得分区Leader副本一直存在,不至于停止对外提供服务,因此提升了高可用性。反之,禁止Unclean领导者选举的好处在于维护了数据的一致性,避免了消息丢失,但牺牲了高可用性。
如果你听说过CAP理论的话,你一定知道,一个分布式系统通常只能同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)中的两个。显然,在这个问题上,Kafka赋予你选择C或A的权利。
可以根据实际业务场景决定是否开启Unclean领导者选举。不过建议不要开启它,还可以通过其他的方式来提升高可用性。如果为了这点儿高可用性的改善,牺牲了数据一致性,那就非常不值当了。
kafka控制器 (Controller)
控制器组件(Controller),是Apache Kafka的核心组件。它的主要作用是在Apache ZooKeeper的帮助下管理和协调整个Kafka集群。集群中任意一台Broker都能充当控制器的角色,但是,在运行过程中,只能有一个Broker成为控制器,行使其管理和协调的职责。换句话说,每个正常运转的Kafka集群,在任意时刻都有且只有一个控制器。
要知道,控制器是重度依赖ZooKeeper的,因此,有必要知道ZooKeeper是做什么的:
- Apache ZooKeeper是一个提供高可靠性的分布式协调服务框架。它使用的数据模型类似于文件系统的树形结构,根目录也是以“/”开始。该结构上的每个节点被称为znode,用来保存一些元数据协调信息。
- 如果以znode持久性来划分,znode可分为持久性znode和临时znode。持久性znode不会因为ZooKeeper集群重启而消失,而临时znode则与创建该znode的ZooKeeper会话绑定,一旦会话结束,该节点会被自动删除。
- ZooKeeper赋予客户端监控znode变更的能力,即所谓的Watch通知功能。一旦znode节点被创建、删除,子节点数量发生变化,抑或是znode所存的数据本身变更,ZooKeeper会通过节点变更监听器(ChangeHandler)的方式显式通知客户端。
- 依托于这些功能,ZooKeeper常被用来实现集群成员管理、分布式锁、领导者选举等功能。Kafka控制器大量使用Watch功能实现对集群的协调管理。
控制器是如何被选出来的?
在前面说过,每台Broker都能充当控制器,那么,当集群启动后,Kafka怎么确认控制器位于哪台Broker呢?
Broker在启动时,会尝试去ZooKeeper中创建/controller节点。Kafka当前选举控制器的规则是:第一个成功创建/controller节点的Broker会被指定为控制器。(类似于setnx)
控制器是做什么的?
控制器是起协调作用的组件,控制器的职责大致可以分为5种
1.主题管理(创建、删除、增加分区)
这里的主题管理,就是指控制器帮助我们完成对Kafka主题的创建、删除以及分区增加的操作。换句话说,当我们执行kafka-topics脚本时,大部分的后台工作都是控制器来完成的。关于kafka-topics脚本,我会在专栏后面的内容中,详细介绍它的使用方法。
2.分区重分配
分区重分配主要是指,kafka-reassign-partitions脚本(关于这个脚本,后面我也会介绍)提供的对已有主题分区进行细粒度的分配功能。这部分功能也是控制器实现的。
3.Preferred领导者选举
Preferred领导者选举主要是Kafka为了避免部分Broker负载过重而提供的一种换Leader的方案。
4.集群成员管理(新增Broker、Broker主动关闭、Broker宕机)
这是控制器提供的第4类功能,包括自动检测新增Broker、Broker主动关闭及被动宕机。这种自动检测是依赖于前面提到的Watch功能和ZooKeeper临时节点组合实现的。
比如,控制器组件会利用Watch机制检查ZooKeeper的/brokers/ids节点下的子节点数量变更。目前,当有新Broker启动后,它会在/brokers下创建专属的znode节点。一旦创建完毕,ZooKeeper会通过Watch机制将消息通知推送给控制器,这样,控制器就能自动地感知到这个变化,进而开启后续的新增Broker作业。
侦测Broker存活性则是依赖于刚刚提到的另一个机制:临时节点。每个Broker启动后,会在/brokers/ids下创建一个临时znode。当Broker宕机或主动关闭后,该Broker与ZooKeeper的会话结束,这个znode会被自动删除。同理,ZooKeeper的Watch机制将这一变更推送给控制器,这样控制器就能知道有Broker关闭或宕机了,从而进行“善后”。
5.数据服务
控制器的最后一大类工作,就是向其他Broker提供数据服务。控制器上保存了最全的集群元数据信息,其他所有Broker会定期接收控制器发来的元数据更新请求,从而更新其内存中的缓存数据。
控制器保存了什么数据?
比较重要的数据有:
- 所有主题信息。包括具体的分区信息,比如领导者副本是谁,ISR集合中有哪些副本等。
- 所有Broker信息。包括当前都有哪些运行中的Broker,哪些正在关闭中的Broker等。
- 所有涉及运维任务的分区。包括当前正在进行Preferred领导者选举以及分区重分配的分区列表。
值得注意的是,这些数据其实在ZooKeeper中也保存了一份。每当控制器初始化时,它都会从ZooKeeper上读取对应的元数据并填充到自己的缓存中。有了这些数据,控制器就能对外提供数据服务了。这里的对外主要是指对其他Broker而言,控制器通过向这些Broker发送请求的方式将这些数据同步到其他Broker上。
控制器故障转移(Failover)
我们在前面强调过,在Kafka集群运行过程中,只能有一台Broker充当控制器的角色,那么这就存在单点失效(Single Point of Failure)的风险,Kafka是如何应对单点失效的呢?答案就是,为控制器提供故障转移功能,也就是说所谓的Failover。
故障转移指的是,当运行中的控制器突然宕机或意外终止时,Kafka能够快速地感知到,并立即启用备用控制器来代替之前失败的控制器。这个过程就被称为Failover,该过程是自动完成的,无需你手动干预。
接下来一起来看一张图,它简单地展示了控制器故障转移的过程。
最开始时,Broker 0是控制器。当Broker 0宕机后,ZooKeeper通过Watch机制感知到并删除了/controller临时节点。之后,所有存活的Broker开始竞选新的控制器身份。Broker 3最终赢得了选举,成功地在ZooKeeper上重建了/controller节点。之后,Broker 3会从ZooKeeper中读取集群元数据信息,并初始化到自己的缓存中。至此,控制器的Failover完成,可以行使正常的工作职责了。
版权归原作者 mming520 所有, 如有侵权,请联系我们删除。