kafka入门 第十二篇 你一定不能错过的kafka控制器
控制器组件(Controller),是Apache kafka的核心组件。它的主要作用是在Apache Zookeeper的帮助下管理和协调整个kafka集群(社区正在计划去掉zookeeper的依赖)。集群中的任意一台Broker都可以成为控制器,在一个正常运行的集群中目前只能有一个控制器。
实际上,在Broker启动的时候,会尝试去zookeeper中创建/controller节点。第一个成功创建/controller的节点Broker会被指定为控制器。
控制器的作用:
- 主题管理:主题的创建、删除、增加分区等。当我们在任意一台Broker中执行kafka-topic脚本时,它们会自动找到控制器,并把工作交给控制器来执行。
- 分区重分配: 分区重分配主要是指,kafka-reassign-partition脚本提供的对已有主题进行细粒度的分配功能。这部分功能也是控制器来实现。
- Preferred(优先)领导者选举:Preferred领导者选举主要是kafka为了避免部分Broker负载过重而提供的一种换Leader的方案。
- 集群成员管理:新增Broker、Broker主动关闭、Broker宕机
- 数据服务: 就是想其他Broker提供数据服务。控制器上保存了最全的集群元数据信息,其他所有Broker会定期接受控制器发来的元数据更新请求,从而更新其内存中的缓存数据。
下面用一张图来说明一下控制器中保存了什么信息:
值的注意的是上面的这些信息,在zookeeper中也保存了一份。每当控制器初始化的时候,它都会从zookeeper上读取对应的元数据并填充到自己的缓存中。有了这些数据,控制器就能对外提供数据库服务了。
故障转移:
我们在前⾯强调过,在 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 完 成,可以⾏使正常的⼯作职责了。
控制器内部设计原理:
在 Kafka 0.11 版本之前,控制器的设计是相当繁琐的,代码更是有些混乱,这就导致社区中很多 控制器⽅⾯的 Bug 都⽆法修复。控制器是多线程的设计,会在内部创建很多个线程。⽐如,控制 器需要为每个 Broker 都创建⼀个对应的 Socket 连接,然后再创建⼀个专属的线程,⽤于向这 些 Broker 发送特定请求。如果集群中的 Broker 数量很多,那么控制器端需要创建的线程就会很 多。另外,控制器连接 ZooKeeper 的会话,也会创建单独的线程来处理 Watch 机制的通知回 调。除了以上这些线程,控制器还会为主题删除创建额外的 I/O 线程。 ⽐起多线程的设计,更糟糕的是,这些线程还会访问共享的控制器缓存数据。我们都知道,多线 程访问共享可变数据是维持线程安全最⼤的难题。为了保护数据安全性,控制器不得不在代码中 ⼤量使⽤ReentrantLock 同步机制,这就进⼀步拖慢了整个控制器的处理速度。 鉴于这些原因,社区于 0.11 版本重构了控制器的底层设计,最⼤的改进就是,把多线程的⽅案改 成了单线程加事件队列的⽅案。我直接使⽤社区的⼀张图来说明。
从这张图中,我们可以看到,社区引⼊了⼀个事件处理线程,统⼀处理各种控制器事件,然后控 制器将原来执⾏的操作全部建模成⼀个个独⽴的事件,发送到专属的事件队列中,供此线程消 费。这就是所谓的单线程 + 队列的实现⽅式。 值得注意的是,这⾥的单线程不代表之前提到的所有线程都被“⼲掉”了,控制器只是把缓存状态 变更⽅⾯的⼯作委托给了这个线程⽽已。 这个⽅案的最⼤好处在于,控制器缓存中保存的状态只被⼀个线程处理,因此不再需要重量级的 线程同步机制来维护线程安全,Kafka 不⽤再担⼼多线程并发访问的问题,⾮常利于社区定位和 诊断控制器的各种问题。事实上,⾃ 0.11 版本重构控制器代码后,社区关于控制器⽅⾯的 Bug 明显少多了,这也说明了这种⽅案是有效的。
针对控制器的第⼆个改进就是,将之前同步操作 ZooKeeper 全部改为异步操作。ZooKeeper 本 身的 API 提供了同步写和异步写两种⽅式。之前控制器操作 ZooKeeper 使⽤的是同步的 API, 性能很差,集中表现为,当有⼤量主题分区发⽣变更时,ZooKeeper 容易成为系统的瓶颈。新 版本 Kafka 修改了这部分设计,完全摒弃了之前的同步 API 调⽤,转⽽采⽤异步 API 写⼊ ZooKeeper,性能有了很⼤的提升。根据社区的测试,改成异步之后,ZooKeeper 写⼊提升了 10 倍! 除了以上这些,社区最近⼜发布了⼀个重⼤的改进!之前 Broker 对接收的所有请求都是⼀视同 仁的,不会区别对待。这种设计对于控制器发送的请求⾮常不公平,因为这类请求应该有更⾼的 优先级。
举个简单的例⼦,假设我们删除了某个主题,那么控制器就会给该主题所有副本所在的 Broker 发送⼀个名为StopReplica的请求。如果此时 Broker 上存有⼤量积压的 Produce 请求,那么这 个 StopReplica 请求只能排队等。如果这些 Produce 请求就是要向该主题发送消息的话,这就 显得很讽刺了:主题都要被删除了,处理这些 Produce 请求还有意义吗?此时最合理的处理顺 序应该是,赋予 StopReplica 请求更⾼的优先级,使它能够得到抢占式的处理。 这在 2.2 版本之前是做不到的。不过⾃ 2.2 开始,Kafka 正式⽀持这种不同优先级请求的处理。 简单来说,Kafka 将控制器发送的请求与普通数据类请求分开,实现了控制器请求单独处理的逻 辑。鉴于这个改进还是很新的功能,具体的效果我们就拭⽬以待吧。