Kafka学习笔记(十五) --- Controller

控制器组件,是Apache Kafka的核心组件,它主要是在Apache Zookeeper的帮助下管理和协调整个Kafka集群。集群中的任意一台Broker都能充当控制器的角色,但是运行过程中只能有一台作为控制器,行使管里和协调的职责。官网有个activeController的JMX指标,可以帮助我们实时监控控制器的存活状态。

  • Zookeeper简介

Apache Zookeeper是一个提供高可靠性的分布式协调框架。他使用的数据模型类似于文件系统的树形结构,根目录也是以 "/" 开始,该结构上的每个节点都被称为 znode,用来保存一些元数据信息。znode按持久性来分可以分为持久性znode和临时znode。持久性znode不会因为Zookeeper集群重启而消失,而临时znode则与创建 该znode的Zookeeper会话绑定,一旦会话结束,该节点会被自动删除。

Zookeeper赋予客户端监控znode变更能力,即所谓的Watch通知功能。一旦znode节点被创建、删除、子节点数量发生变化,亦或是znode所存数据本身的变更,Zookeeper会通过节点变更监听器( ChangHandler )的方式显示通知客户端。依托于这些功能,Zookeeper常被用来实现集群成员管理、分布式锁、领导者选举等。Kafka控制器大量使用Watch功能实现对集群的协调管理。下图展示了Kafka在Zookeeper中创建的znode分布,你可以体会一下kafka对Zookeeper的依赖。

                               

  • Controller

1.  如何被选举?

在Kafka集群启动后,Kafka怎么确认控制器位于哪台Broker?当Broker启动时,会尝试去Zookeeper中创建/controller节点,那么第一个成功创建/controller节点的Broker被指定为控制器。

2. 可以做什么?

主题管理:主题管理,就是控制器帮助我们完成Kafka主题的创建,删除以及分区增加操作。当我们执行kafka-topic脚本是,大部分后台工作都是有控制器完成。

分区重分配:kafka-reassign-partition脚本提供的对已有主题分区进行细粒度的分配功能。

Preferred领导者选举:kafka为了避免部分Broker负载过重而提供的一种换Leader方案。

集群成员管理:包括自动检测新增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关闭或宕机了,从而进行善后。

数据服务:控制器上保存最全的集群元数据信息,其他所有Broker会定期接收控制器发来的元数据更新请求。

3. 控制器保存了那些数据?

                                

这张图几乎把我们能想到的所有Kafka集群的数据都囊括进来了。这里比较重要的数据有:

所有主题信息:包括具体的分区信息,比如领导者副本是谁,ISR集合中有哪些副本。

所有Broker信息:包括当前有哪些运行中的Broker,哪些正在关闭的Broker等。

所有涉及运维任务的分区:包括当前正在进行Preferred领导者选举以及分区重分配的分区列表。

以上所有数据其实在Zookeeper中也保存一份,每当控制器初始化时,都会在Zookeeper中读取对应的元数据并填充到自己的缓存中。有了这些数据,控制器就可以对外提供服务,通过向这些Broker发送请求的方式将这些数据同步到其他Broker上。

4. 故障转移

在Kafka集群运行过程中,只能有一台Broker充当控制器角色,那么就存在单点失效的风险,Kafka是怎么应对的?答案就是控制器提供故障转移功能,也就是Failover。故障转移指的是,当运行中的控制器突然宕机或意外终止时,Kafka能够快速感知到,并立即启用备用控制器来代替它。这就是Failover,自动完成,无需手动干预。

                                   

图中,最开始,Broker是控制器,当它宕机后,Zookeeper通过Watch机制感知到并删除临时节点。之后,所有存活的Broker开始竞选新的控制器身份。Broker3赢得选举,成功在Zookeeper上重建/controller节点。之后,Broker3会从Zookeeper中读取集群元数据信息,并初始化自己的缓存中。

5. 控制器设计原理

在Kafka 0.11版本之前,控制器的设计相当繁琐,代码更是混乱,这就导致社区中很多控制器方面的BUG无法修复。控制器是多线程的设计,会在内部创建很多个线程。比如,控制器需要为每个Broker都创建一个socket连接,然后再创建专属线程,用于向这些特定Broker发送特定请求。如果集群中Broker数量很多,那么控制器需要创建线程就会很多。另外,控制器连接Zookeeper的会话,也会创建单独线程来处理Watch机制的通知回调。除了以上这些线程,控制器还会为主题删除创建额外IO线程。比起多线程,更糟糕的是,这些线程还会访问共享的控制器缓存数据,这个是维持线程安全最大的难题,控制器不得不在代码中大量使用ReentrantLock同步机制,更拖慢了整个控制器的处理速度。

鉴于以上原因,社区于0.11版本重构了控制器底层设计,最大改进就是,把多线程方案改成单线程加事件队列的方案。下图中,社区引入一个事件处理线程,统一处理各种控制器事件,然后控制器将原来执行的操作全部建模成一个个独立事件,发送到专属时间队列中,供此线程消费。

                                           

注意,这个改动不代表之前的线程都被“干掉”了,控制器只是把缓存状态变更的方面工作委托给这个线程了。改动最大的好处是,不需要重量级的线程同步机制维护线程安全,Kafka不用再担心多线程并发访问问题,非常利于社区定位和诊断控制器的各种问题。

第二个改进是,将之前同步操作Zookeeper全部改为异步操作。Zookeeper本身的API提供了同步写和异步写两种方式。因为,如果一直采用同步写方式,当有大量主题分区变更时,Zookeeper容易成为性能瓶颈。根据社区的测试,改成异步之后,Zookeeper写入提升10倍。

社区最近又发布了一个重大改进,之前Broker对接收的所有请求一视同仁,不会区别对待。这种设计,对于控制器发送的请求非常不公平。比如,假若我们要删除某个主题,那么控制器就会给该主题所有副本所在Broker发送一个名为StopReplica请求。如果此时Broker存有大量积压Producer请求,那么这个StopReplica只能排队等。如果这些请求就是向该主题发送消息,就显得很讽刺。这时最合理的处理顺序是,赋予StopReplica请求最高优先级,是他能得到抢占式处理。但是,这在2.2版本之以后才能做到。

标注:这个系列文章是本人在极客时间专栏---kafka核心技术与实战中的学习笔记

    https://time.geekbang.org/column/article/101171

发布了37 篇原创文章 · 获赞 20 · 访问量 4949

猜你喜欢

转载自blog.csdn.net/qq_24436765/article/details/102707298