(二)Kafka之集群架构原理

原理至关重要,面试的时候不可能问你命令的,都是问原理,懂了原理线上如果使用kafka出了问题才可能快速定位,而不是一脸蒙圈。必须要明白原理,如果不说原理直接实战,就真成搬砖了。

Topic

创建一个TopicA的主题,3个分区分别存储在不同的服务器,注意Topic是一个逻辑上的概念。

(二)Kafka之集群架构原理_第1张图片

Partition & Partition副本

Kafka的topic可以划分成一个或多个partition,Partition 是物理上的概念。如果一个topic的副本数设为3,那么每个partition对应还会有3个相同的副本。下图我们对TopicA的分区0,1,2分别设置了3个副本,再分别存储在broker0,1,2。

(二)Kafka之集群架构原理_第2张图片

日志分段存储

(二)Kafka之集群架构原理_第3张图片

由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制。

它将每个 Partition 分为多个 Segment,每个 Segment 对应两个文件:“.index” 索引文件和 “.log” 数据文件。

Leader & Follow

而且每个副本都是有角色之分的,它们会选举一个副本作为leader,其余的为follower。生产者在发送数据的时候,是直接发送到 leader partition,然后follower partition自行去leader进行数据同步,消费者消费数据的时候,也是从leader中消费数据。(下图在TopicA-partition-0在broker0是leader,同理其他TopicA-partition-N也有leader)

(二)Kafka之集群架构原理_第4张图片

Consumer & Consumer group

一个消费组由一个或多个消费者实例组成,便于扩容与容错。一个分区不会让同一个消费者组里面的多个消费者去消费,一个消费者是可以去消费多个分区的数据的。

(二)Kafka之集群架构原理_第5张图片

(二)Kafka之集群架构原理_第6张图片

Kafka的网络设计

(二)Kafka之集群架构原理_第7张图片

  1. 客户端将请求发送给Acceptor,broker里有3个processor的线程(默认是3),Acceptor不会对客户端的请求做任何的处理,而是封装成socketChannel,然后发送给3个processor线程,形成一个队列。发送的方式是轮询,就是发送给第一个processor,然后是第二个,第三个...
  2. 消费者线程会以request请求去消费这些socketChannel;
  3. 线程池里面默认有8个ReaderThreadPool线程,这些线程是用来处理request的,解析请求,返回响应结果response;
  4. processor会从response中读取响应数据,然后再返回给客户端。

所以如果我们需要对kafka进行增强调优,增加processor并增加线程池里面的处理线程,就可以达到效果。request和response那一块部分其实就是起到了一个缓存的效果,是考虑到processor们生成请求太快,线程数不够不能及时处理的问题。
所以这就是一个加强版的reactor网络线程模型。

Kafka零拷贝

传统IO:

(二)Kafka之集群架构原理_第8张图片

//读取文件,再用socket发送出去
buffer = File.read 
Socket.send(buffer)

1、第一次:将磁盘文件,读取到操作系统内核缓冲区;
2、第二次:将内核缓冲区的数据,copy到application应用程序的buffer;
3、第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
4、第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。

传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的。实际IO读写,需要进行IO中断,需要CPU响应中断(带来上下文切换),尽管后来引入DMA来接管CPU的中断请求,但四次copy是存在“不必要的拷贝”的。

零拷贝:

(二)Kafka之集群架构原理_第9张图片

Kafka使用的zero-copy的应用程序要求内核直接将数据从磁盘文件拷贝到套接字,而无需通过应用程序。零拷贝不仅大大地提高了应用程序的性能,而且还减少了内核与用户模式间的上下文切换。

zookeeper在kafka集群中的作用

1、Broker注册

Broker是分布式部署并且相互独立,但是需要有一个注册系统能够将整个集群中的Broker管理起来,此时就使用到了Zookeeper。在Zookeeper上会有一个专门用来进行Broker服务器列表记录的节点:/brokers/ids

每个Broker在启动时,都会到Zookeeper上进行注册,即到/brokers/ids下创建属于自己的节点,如/brokers/ids/[0...N]。

Kafka使用了全局唯一的数字ID来指代每个Broker服务器,创建完节点后,每个Broker就会将自己的IP地址和端口信息记录到该节点中去。其中,Broker创建的节点类型是临时节点,一旦Broker宕机,则对应的临时节点也会被自动删除。

2、Topic注册

在Kafka中,Topic的消息分区与Broker的对应关系也都是由Zookeeper在维护,由专门的节点来记录,如:/borkers/topics

Kafka中每个Topic都会以/brokers/topics/[topic]的形式被记录,如 /brokers/topics/login 和 /brokers/topics/search 等。Broker服务器启动后,会到对应Topic节点(/brokers/topics)上注册自己的Broker ID,并写入该Topic的分区总数,如/brokers/topics/login/3->2,这表示Broker ID为3的节点对"login"这个Topic提供了2个分区进行消息存储。同样,这个分区节点也是临时节点。

3、消费者注册

①、注册节点到消费者分组。每个消费者服务器启动时,都会到Zookeeper的指定节点下创建一个属于自己的消费者节点,例如/consumers/[group_id]/ids/[consumer_id],完成节点的创建后,消费者就会将自己订阅的Topic信息写入该临时节点。

②、对消费者分组中的消费者的变化注册监听。每个 消费者都需要关注所属消费者分组 其他消费者服务器的变化情况,即对/consumers/[group_id]/ids节点注册子节点变化的Watcher监听,一旦发现消费者新增或减少,就触发消费者的负载均衡。

4、分区 与 消费者 的关系

在Kafka中,规定了每个消息分区只能被同组的一个消费者进行消费,因此,需要在 Zookeeper 上记录 消息分区 与 Consumer 之间的关系,每个消费者一旦确定了对一个消息分区的消费权力,需要将其Consumer ID 写入到 Zookeeper 对应消息分区的临时节点上,例如:

/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]

其中,[broker_id-partition_id]就是一个 消息分区 的标识,节点内容就是该 消息分区 上 消费者的Consumer ID。

5、消息消费进度Offset 记录

在消费者对指定消息分区进行消费中,需要定时地将分区消息的消费进度Offset记录到Zookeeper上,以便在该消费者进行重启或者其他消费者重新接管该消息分区的消息消费后,能够从之前的进度开始继续进行消息消费。Offset在Zookeeper中由一个专门节点进行记录,其节点路径为:

/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]

节点内容就是Offset的值。

6、生产者负载均衡

由于同一个Topic消息会被分区,并被分布在多个Broker上,因此,生产者需要将消息合理地发送到这些分布式的Broker上,那么如何实现生产者的负载均衡,Kafka支持传统的四层负载均衡,也支持Zookeeper方式实现负载均衡。

(1) 四层负载均衡,通常,一个生产者只会对应单个Broker,然后该生产者产生的消息都发往该Broker。这种方式逻辑简单,每个生产者不需要同其他系统建立额外的TCP连接,只需要和Broker维护单个TCP连接即可。但是,其无法做到真正的负载均衡,因为实际系统中的每个生产者产生的消息量及每个Broker的消息存储量都是不一样的,如果有些生产者产生的消息远多于其他生产者的话,那么会导致不同的Broker接收到的消息总数差异巨大,同时,生产者也无法实时感知到Broker的新增和删除。

(2) 使用Zookeeper进行负载均衡,由于每个Broker启动时,都会完成Broker注册过程,生产者会通过该节点的变化来动态地感知到Broker服务器列表的变更,这样就可以实现动态的负载均衡机制。

7、消费者负载均衡

与生产者类似,Kafka中的消费者同样需要进行负载均衡来实现多个消费者合理地从对应的Broker服务器上接收消息。

你可能感兴趣的:(kafka)