一、Kafka核心总控制器

  定义:kafka集群中的一个负责管理所有分区和副本的状态的broker

PS:kafka单台机器也叫集群。

  职能:选举新的leader副本、ISR变更通知所有broker更新其元数据、让新分区被其他节点感知。

  • 当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本。
  • 当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有broker更新其元数据信息。
  • 当使用kafka-topics.sh脚本为某个topic增加分区数量时,同样还是由控制器负责让新分区被其他节点感知到。

Controller选举原理

  • kafka集群启动时,每个borker去ZK上创建一个“/controller 临时节点”,谁创建成功谁就是Controller
  • 当这个controller角色的broker宕机了,此时ZK上的临时节点会消失,集群里其他broker会一直监听这个临时节点,发现临时节点消失了,就竞争再次创建临时节点,ZK又会保证只有一个broker成为新的controller。

Partition副本选举Leader原理

  • controller感知到分区leader所在的broker挂了,则会从ISR列表里挑第一个broker作为leader。
  • 若参数unclean.leader.election.enable为true,代表在ISR列表里所有副本都挂了,则可以在ISR列表以外的副本中选leader。

副本进入ISR的条件

  • 副本节点不能产生分区,必须能与zookeeper保持会话以及跟leader副本网络连通。
  • 副本能复制leader上的所有写操作,并且不能落后太多。

  PS:之所以取ISR中的第一个broker升级Leader,是因为第一个broker最先放进ISR 列表,可能是同步数据最多的副本。

二、消费者offset记录机制

offset记录机制

  • 每个consumer会定期将自己消费分区的offset提交给kafka内部topic:__consumer_offsets
  • 提交过去的时候,keyconsumerGroupId+topic+分区号value就是当前offset的值。
  • kafka会定期清理topic里的消息,最后就保留最新的那条数据。

  PS:因为__consumer_offsets可能会接收高并发的请求,kafka默认给其分配50个分区(可以通过offsets.topic.num.partitions设置),这样可以通过加机器的方式抗大并发。

kafka_exporter 集群 kafka集群broker_kafka_exporter 集群

kafka_exporter 集群 kafka集群broker_数据_02

kafka_exporter 集群 kafka集群broker_kafka_03

三、消费者Rebalance机制

Rebalance机制

  定义:如果消费组里的消费者数量有变化或消费的分区数有变化,kafka会重新分配消费者和消费分区的关系。

  PS:每个消费者都会有消费组,如果不指定会生成默认的组。

  PS:rebalance只针对subscribe这种不指定分区消费的情况,如果通过assign这种消费方式指定了分区,kafka不会进行rebanlance。

  PS:rebalance过程中,消费者无法从kafka消费消息!!!【尽量避免在系统高峰期时发生重平衡。】

 触发场景:

  • 1、消费组里的consumer增加或减少了
  • 2、动态给topic增加了分区
  • 3、消费组订阅了更多的topic

Rebalance过程

设计原理:分区方案制定过程中有两个组长,消费者组长负责制订分区策略,生产者组长负责通知其他消费者分区策略。【同一个消费者组中的消费者没有联系,需要通过kafka组长来协调。

kafka_exporter 集群 kafka集群broker_数据_04

 两个重要角色:

  • 组协调器【生产者组长】:每个消费者组都会选择一个broker作为自己的组协调器coordinator,负责监控这个消费组里的所有消费者的心跳,以及判断是否宕机,然后开启消费者rebalance
  • 消费组协调器【消费者组长】负责制定分区方案,并与组协调器进行通信

PS:消费者组长不是我们所说的分区Leader!这个概念要区分好!

第一阶段:选择组协调器【生产者组长】

  消费者组中的每个consumer启动时会向kafka集群中的某个节点发送查找组协调器的请求,并跟其建立网络连接

组协调器选择方式

  公式:hash(consumer_group_id) % _consumer_offsets主题的分区数

根据公式获取到分区地址后,这个分区leader对应的broker就是这个消费者组的组协调器。

第二阶段:消费者入组

  在成功找到消费组所对应的组协调器后就进入消费组入组的阶段,在此阶段的消费者会向组协调器发送入组请求。

  组协调器从一个消费者组中选择第一个加入group的consumer作为消费者组长,把消费者组的情况发送给这个broker,接着这个broker会负责制定分区方案

第三阶段:组协调器下发分区方案

  消费者组长通过给组协调器发送下发分区策略请求,接着组协调器就把分区方案下发给各个consumer,他们会与指定分区的leader对应的broker进行网络连接以及消息消费。

Rebalance分区分配策略

  • range【默认】:根据分区数/消费者数量,然后给每个消费者分配n个分区。【消费者1:0~3  消费者2:4~6  消费者3:7~9】
  • round-robin:轮询分配机制。【消费者1:0、3、6  消费者2:1、4、7  消费者3:2、5、8】
  • sticky:粘性轮询机制。【当分区增加或者增加消费者时,只会重新分配挂掉的那台或者新的分区。】

PS:range和轮询当分区增加或者增加消费者时,会重新轮询。而粘性策略则不会,粘性策略只会重新分配挂掉的那台或者新的分区。

四、生产者发布消息机制

写入方式

push模式将消息发布到broker,每条消息都被append到patition中,属于顺序写磁盘

PS:顺序写磁盘效率比随机写内存要高,可以保障kafka吞吐量。

消息路由

  producer发送消息到broker时,会根据分区算法选择将其存储到哪一个partition。其路由机制为:

  • 1、指定了patition,则直接使用
  • 2、未指定patition但指定key,通过对key的value进行hash选出一个patition。【key的组成见本文上半部分的解析】
  • 3、patition和key都未指定,使用轮询选出一个 patition。

写入流程

kafka_exporter 集群 kafka集群broker_kafka_05

五、高水位

基本概念

  • HW:高水位
  • LEO:日志末端位移

kafka_exporter 集群 kafka集群broker_kafka_exporter 集群_06

HW一个partition对应的ISR中最小的LEO(log-end-offset), consumer最多只能消费到HW所在的位置

每个replica都有HW,leader和follower各自负责更新自己的HW的状态

PS:对于leader新写入的消息,consumer不能立刻消费,leader会等待该消息被所有ISR中的replicas同步后更新HW,此时消息才能被consumer消费。

快乐图解~

kafka_exporter 集群 kafka集群broker_kafka_exporter 集群_07

kafka_exporter 集群 kafka集群broker_kafka_exporter 集群_08

六、日志分段存储原理

topic名称+分区号命名:

kafka_exporter 集群 kafka集群broker_kafka_exporter 集群_09

  消息在分区内是分段(segment)存储,每个段的消息都存储在不一样的log文件里,这种特性方便old segment file快速被删除,kafka规定了一个段位的log文 件最大为1G,做这个限制目的是为了方便把 log 文件加载到内存去。

# 【部分消息的offset索引文件】,kafka每次往分区发4K(可配置)消息就会记录一条当前消息的offset到index文件
# 如果要定位消息的offset会先在这个文件里快速定位,再去log文件里找具体消息
00000000000000000000.index

# 【消息存储文件】,主要存offset和消息体
00000000000000000000.log

# 【消息的发送时间索引文件】,kafka每次往分区发4K(可配置)消息就会记录一条当前消息的发送时间戳与对应的offset到timeindex文件
# 如果需要按照时间来定位消息的offset,会先在这个文件里查找
00000000000000000000.timeindex

00000000000005367851.index
00000000000005367851.log
00000000000005367851.timeindex

00000000000009936472.index
00000000000009936472.log
00000000000009936472.timeindex

当储存满时每个log文件的大小是一样的,但是存储的message数量是不一定相等的(每条的message大小不一致)。

  PS:每个日志段文件最大为1G

  • 文件的命名是以该segment最小offset来命名的,如00000000000000.index存储offset为0~5367850的消息...以此类推。
  • kafka就是利用分段+索引的方式来解决查找效率的问题。