前言

该篇博客将简述Kafka的一些深入知识,写出博主的总结和配图。

环境:
Kafka-2.1.1 + Kafka 集群
参考:
《Kafka权威指南》
注:以下所有内容皆是Kafka-0.9 或0.10 版本特性。或许新版本(如2.1.1)在某些部分会有改动,这里仅供参考。

1. 集群成员关系

Kafka使用Zookeeper来维护集群成员的信息。每个broker都有一个唯一标识符,这个标识符可以在配置文件里指定(kafka-2.1.1/config/server.properties),也可以自动生成。在broker启动的时候,它通过创建临时节点把自己的ID注册到Zookeeper。
Kafka组件订阅Zookeeper的 /brokers/ids 路径(broker在Zookeeper的注册路径),当有broker加入集群或退出集群时,Kafka就可以获得通知。
如果你要启动另一个具有相同ID的broker,会得到一个错误,因为Zookeeper中已经有一个具有相同ID的broker。
但是,假如ID=1的broker从在线状态突发崩溃,它在Zookeeper上的注册节点就会消失。此时,如果另一个ID=1的broker启动,它会立即加入集群,并拥有与刚才崩溃broker 相同的分区和主题。

进入Zookeeper 客户端:

zkCli.sh -server master:2181

查看:

kafka win10 可视化链接工具 kafka客户端连接_客户端


上图中,第一次查看时没有启动Kafka服务。然后,启动所有节点的Kafka服务,再次查看,出现所有broker的ID。

2. 控制器

2.1 控制器的概念

控制器其实就是一个broker,只不过它除了具有一般broker的功能之外,还负责分区首领的选举。

集群里第一个启动的broker通过在Zookeeper里创建一个临时节点 /controller 让自己成为控制器。其他broker在启动时也会尝试创建这个节点。不过,它们会受到一个“节点已存在”的异常,然后“意识”到控制器节点已经存在。不过,它们会在控制器节点上创建Zookeeper watch 对象,这样它们就可以收到这个节点的变更通知。这种方法可以确保集群里只有一个控制器存在。

kafka win10 可视化链接工具 kafka客户端连接_数据请求_02


2.2 控制器断开连接后,创建新控制器

如果控制器被关闭或者与Zookeeper断开连接,Zookeeper上的临时节点就会消失。集群里其他broker通过watch对象得到控制器节点消失的通知,它们会尝试让自己成为新的控制器。第一个在Zookeeper上成功创建控制器节点的broker就会成为新的控制器,其他节点会收到“节点已存在”的异常。每个新选出的控制器会根据Zookeeper的条件递增一个全新的、数值更大的controller epoch。其他broker在知道当前的epoch后,如果收到由控制器发出的包含较旧的epoch的消息,就会忽略它们。

kafka win10 可视化链接工具 kafka客户端连接_深入Kafka_03


2.3 控制器确定新的首领

当控制器发现一个broker已经离开集群(观察相关Zookeeper的路径),它就知道那些失去首领的分区需要一个新的分区(这个首领在该broker上)。控制器通过遍历分区,并确定谁应该成为新首领(简单来说就是分区副本列表里的下一个副本),然后(控制器)向所有包含新首领或现有跟随者的broker发送请求。请求消息里包含了谁是新首领以及谁是分区跟随者的信息。随后,新首领开始处理生产者和消费者的请求,而跟随者开始从新首领哪里复制消息。

总结
简单来说,Kafka使用Zookeeper的临时节点来选举控制器,并在节点加入或退出集群时通知控制器。控制器负责在节点加入或离开集群时进行分区首领的选举。控制器使用epoch来避免“脑裂”(指有两个节点认为自己是控制器)。

3. 复制

复制功能是Kafka架构的核心。Kafka把自己描述成“一个分布式的、可分区的、可复制的提交日志服务”。 复制保证了Kafka的可用性和持久性。

3.1 副本类型
Kafka使用主题来描述组织数据,每个主题被分为若干个分区,每个分区有多个副本。
副本有下面两种类型:

  • 首领副本
    每个分区都有一个首领副本。为了保证一致性,所有生产者和消费者请求都会经过这个副本。
  • 跟随者副本
    首领以外的副本都是跟随者副本。跟随者副本不处理客户端请求,唯一任务就是从首领副本哪里复制消息,保持与首领的一致性。如果首领发生崩溃,其中一个跟随者会被提升为新首领。

3.2 跟随者复制消息
一个跟随者副本先请求消息1,接着请求消息2,然后请求消息3,在收到这3个请求的响应之前,它不会发送第4个请求。如果,跟随者发送第4个请求,首领就知道它已经收到前面3个请求的响应。
如果跟随者在10s 内没有请求任何消息,或者虽然在请求消息,但在10s 内没有请求最新的消息,那它就会被认为是不同步的。 如果一个副部无法与首领保持一致,在首领失效时,它就不可能成为新的首领。
持续请求得到最新消息的副本被称为同步的副本
除了当前首领外,每个分区都有一个首选首领–创建主题时所选定的首领就叫做首选首领。通过一定条件检查后,该首选首领会成为当前首领。
一般来说,在分区副本清单里的第一个副本就是首选首领。

4. 处理请求

broker的大部分工作就是处理客户端、分区副本和控制器发送给分区首领的请求。Kafka提供了一个二进制协议(基于TCP),指定了请求消息的格式和broker如何对请求作出响应。

broker会在它所监听的每一个端口上运行一个Acceptor 线程。这个线程会创建一个连接,并把它交个Processor线程处理。Processor(也称“网络线程”)的数量是可以配置的。网络线程负责从客户端请求消息,把它们放进请求队列,然后从响应队列响应消息,把它们发送给客户端。

Kafka处理请求的内部流程

kafka win10 可视化链接工具 kafka客户端连接_数据_04

4.1 客户端如何知道向那个broker发送请求?

客户端使用了另一种请求类型,也就是元数据请求。这种请求包含了客户端感兴趣的主题列表(包括主题所包含的分区、每个分区有哪些副本、那个副本是首领)。元数据请求可以发送给任何一个broker,因为所有broker都缓存了这些信息。

客户端会时不时的发送元数据请求,用来更新最新的主题列表。

kafka win10 可视化链接工具 kafka客户端连接_数据_05


4.2 生产者请求

acks 参数指定了需要多少个broker 确认才可以认为一个消息时写入成功的。

acks 取值有三种 0、1或all

  • 0:生产者发送消息后,不需要等待broker相应
  • 1:只要首领收到消息就成功
  • all:所有的同步副本收到消息才算写入成功

如果acks =0或1,broker会立即返回响应,如果acks=all,请求会先保存在叫做炼狱的缓冲,知道首领发现所有跟随者副本复制完消息,响应才会返回。

4.3 获取请求
broker处理请求的方式与处理生成请求的方式很相似。客户端发送请求,向broker请求主题分区特定偏移量的消息。比如,“请把主题Test-分区0-偏移量从50开始的消息,以及主题Test-分区3-偏移量从60开始的消息发给我”。客户端还可以指定broker最多从一个分区返回多少数据。这个限制非常重要,因为客户端需要有足够多的内存存储返回的数据。

另外,客户端发送请求后,首领会检查请求是否有效,如果无效将返回错误。
如果有效,broker将按照客户端指定的数量从分区读取消息,再返回给客户端。Kafka使用零复制技术向客户端发送消息。也就是说,Kafka直接把消息从文件(Linux文件系统缓存)里发送到网络通道,而不需要经过任何缓存区

除了设置返回数据的上限,还可以设置下限,如:把下限设置为10KB,如果请求的消息不足10KB,那么broker会等待,直到数据达到10KB。当然,不会一直等待broker累积数据,客户端可以设置一个超时时间,如果在X毫秒内,没有累积到10KB,broker也把消息返回给客户端。

除此之外,并不是所有保存在分区首领上的数据都可以被客户端读取。大部分客户端只能读取已经被写入同步副本的消息(跟随者副本也不行)。分区首领知道每个消息会被复制到那个副本上,在消息还没有被写入所有同步副本之前,是不会发送给消费者的。请求这些消息的请求会得到空的响应,而不是错误。

4.4 其他请求
目前为止,讨论了Kafka最为常见的请求:元数据请求、生产请求、获取请求。
在0.10版本中,Kafka可以处理20种不同的请求(2.1.1版本博主还不清楚)
另外,Kafka决定不再使用Zookeeper保存偏移量,而保存在特定的主题中(__consumer_offsets)。

在以前的版本中,主题的创建必须通过命令行工具来完成。在后面的版本中,增加了CreateTopicRequest 请求类型,这样,就可以直接向broker请求创建新的主题。(简单理解就是,如果在API中向主题A写消息,但主题A并不存在,则会使用默认的配置来创建主题A)。

查看偏移量:

kafka-console-consumer.sh --bootstrap-server master:9092 --from-beginning --topic __consumer_offsets

kafka win10 可视化链接工具 kafka客户端连接_深入Kafka_06


除此之外,在元数据请求消息和响应消息里添加了一个新的version字段。0.9.0版本中versinotallow=0,0.10.0版本中versinotallow=1。(现在版本有兴趣的可以查查官网)。

如果使用0.10.0的客户端发送versinotallow=1的请求给0.9.0版本的broker,这个版本的broker不知道如何处理这个请求,就会返回一个错误。所以,建议先升级broker,再升级客户端。

在0.10.0版本中,加入了ApiVersionRequest请求–客户端可以询问broker支持哪些版本的请求,然后使用正确的版本与broker通信。

完!