kafka的根本体系结构
一个残缺的kafka消息中间件应该蕴含如下几个节点:

  1. 生产者:生产音讯的节点
  2. 消费者:生产音讯的节点
  3. broker:接管生产者发送音讯存储的节点
  4. zookeeper:治理保护broker集群,保留broker集群的元数据信息,保障集群高可用。


kafka音讯存储形式
kafka是如何进行音讯存储的?如何保障整个集群的负载平衡和高可用?解说之前须要对kafka的一些基本概念做下补充
主题:生产者发送一个音讯都必须挂靠一个主题,也就是说咱们发的音讯都是发往这个主题下
分区:实质上就是存储音讯的一个物理文件概念,分区内的每一条音讯都是有序的,遵循FIFO准则
音讯:发送的一条条音讯就是寄存在分区之上的
正本:kafka往往都会搭建成集群,所以雷同音讯是有多份的,java培训也就是分区在集群上存在多份。而集群中,分区都会分为leader正本和follower正本,leader正本次要负责读/写申请,而follower正本次要负责同步leader正本数,保障leader正本节点宕机时,依然可能通过选举机制保障集群高可用
broker:kafka集群中每一个节点
生产者:生成音讯的节点
消费者:生产音讯的节点,同一条消只能被生产组内的一个消费者生产
生产组:kafka 中每个消费者都会属于一个消费者组
ok,通过下图再来直观感触下以上几个基本概念

音讯存入kafka集群中须要有几个步骤

  1. 创立主题指定分区数量和正本数,同时会依据调配算法将分区正本调配到指定broker
  2. 生产者向该主题发送音讯
  3. kafka依据音讯的key确定对应的分区(如果没有key主动生一个),将音讯发往该分区的leader正本节点

2、3步没什么可说的,着重看下kafka是如何将分区正本调配给集群broker的,来做到负载平衡。
现假如主题A有x个分区,3个正本数。正本调配规定会依据设定的正本数量对主题下的每个分区进行逐个调配,首先对分区0的正本数量调配完后,紧接着才调配分区1的正本数量。而不是把主题的所有分区进行第一个正本数的调配,再接着第二个正本数的调配。说得有点绕,能够看下图了解了解:

那么具体调配规定的大抵算法是这样子的:

  1. 随机确定分区0第一个正本的broker地位,此时该分区0调配到的broker即为咱们leader正本存储的节点。(再强调一遍,对于读/写分区0的音讯都在这个节点进行)
  2. 随机确定分区0下一副本调配到broker的偏移步长,而后对残余正本数基于这个偏移步长定位broker

具体计算公式如下
分区0第一个正本(leader正本)的broker地位:
(分区0的索引地位 + 随机起始索引 ) % broker数
分区0残余正本broker地位:
val shift = 1+(随机的偏移步长+ 第i个正本的索引i ) % (broker数 -1 )
最终地位 = (第一个正本调配的broker地位 + shift) % broker数
随机的偏移步长:调配下一个分区的时候,会在上一个分区偏移步长之上加1

通过随机起始索引和随机的偏移步长,可能尽量的保障所有分区的leader正本平均的调配到kafka集群。而leader正本的节点负责所有的读写操作,这样就保障不会因为leader正本散布过于集中而导致负载不平衡问题。
生产者端外围解决流程
生产者在创立音讯的时候,必须在broker上创立一个主题并指定分区数和分区正本数量,尔后音讯发送时会往该主题下的分区进行发送。主题创立结束后,进行音讯发送,能够分为如下几个外围步骤

  1. 实例化外围组件

此阶段会实例化一些外围数据 如broker集群的外围元数据信息、可用的分区列表、音讯key和value的序列化形式。

  1. 发送音讯

首先会将音讯填入缓冲区而后另开线程从缓冲区中批量取出数据发送到broker,缓冲区的数据大体设计构造如下。

填入缓冲区的规定会依据音讯的key计算出音讯须要放入那个分区,分区外部的音讯是有序的,同时是一种先进先出的双端队列构造(FIFO)。之所以是双端队列构造是因为它外部有一个重试机制,在音讯发送失败时,保障下次取的音讯还是那个未发送的。而在音讯写入缓冲区的时候,只须要加在队列的尾部即可。整个发送音讯的大体外围流程如下:

消费者端外围解决流程
生产端进行音讯生产的次要外围流程分为以下几个步骤:

  1. 生产组内的消费者依据调配策略,订阅分区
  2. 从指定分区中拉取音讯,进行生产
  3. 提交生产偏移量

• 生产组内的消费者依据调配策略订阅分区

kafka中提供了3种调配策略:RoundRobinAssignor、RangeAssignor、StickyAssignor
RoundRobinAssignor:round-robin 是一种轮询的策略形式,它将订阅主题的分区和消费者进行排序,而后将所有主题的分区平均调配给生产组内的每个消费者(PS:相似与咱们斗地主发牌的形式,分区就是牌,消费者就是玩游戏的人)。
假如有两个主题 t0、t1 别离有3个分区(PS:总共有6个分区 t0p0,t0p1,t0p2,,t1p0,t1p1,t1p2),同一个生产组内有两个消费者(c0,c1)进行订阅,那么分区的调配最终后果如下:
c0:[t0p0,t0p2,t1p1]
c1:[t0p1,t1p0,t1p2]


RangeAssignor: range的调配策略(默认的调配策略),首先对消费者依照字典程序进行排序,而后对一个主题下的所有分区平均调配,调配残余的分区就会依照消费者程序逐个调配给每个消费者,调配完后接着调配下一个主题的分区,逻辑同第一个一样。
假如有两个主题 t0、t1 别离有3个分区(PS:总共有6个分区 t0p0,t0p1,t0p2,t1p0,t1p1,t1p2),那么分区调配的最终后果如下:
c0:[t0p0,t0p2,t1p0,t1p2]
c1:[t0p1,t1p1]

StickyAssignor: 是对于 round-robin和range 更近一步的优化。因为这两种策略当存在同一个生产组内消费者订阅的主题不一样时,那么么可能会存在某个消费者调配的分区较多的状况,导致不平衡问题。而sticky策略对于这种状况也能进行更优的调配,同时对于生产组内新上线/下线消费者时,会基于之前已调配的分区上进行调配,而不是从新进行调配,它尽可能的保障少挪动分区(PS:round-robin和range策略对于新上线、下线消费者时,会从新进行分区调配)
举个例子有3个同属于一个生产组的Consumer:C0、C1、C2,都订阅了4个Topic:T0、T1、T2、T3,每个Topic有2个分区
那么StickyAssignor的调配后果如下图所示(减少RoundRobinAssignor调配作为比照):

• 从指定分区中拉取音讯,进行生产

fetcher.fetchedRecords判断是否有数据,无数据调用fetcher.sendFetches()拉取数据fetcher.sendFetches() 会依据元数据信息,确定消费者须要往哪几个分区拉取音讯,同时创立FetchRequest的申请音讯(此阶段会确定区音讯偏移量的起始地位),而后调用ConsumerNetworkClient.send 办法获取音讯。
这边须要着重讲一下消费者如何确定生产偏移量的起始地位,起始地位的确定跟auto.offset.reset配置值有比拟大的关系,次要有两个外围值earliest和latest(默认策略)。消费者会先查看本地是否曾经有提交的偏移量,如有那么依据以后偏移量持续往下读取。如果没有那么会依据auto.offset.reset的值确定偏移量。earliest和latest都会向kafka集群中获取已提交的偏移地位,基于该偏移量持续生产。如果没有已提交的偏移量,earliest会从头生产,latest将生产新产生的音讯(PS:latest这种策略如果新建生产组的话在公布利用阶段呈现音讯失落的状况)
不太明确的话举个例子,假如6条音讯,生产组A还有两条音讯未生产。
当reset为earliest:重启生产组A,收到2条音讯;新建生产组B,收到6条音讯.
当reset为latest:重启生产组A,收到2条音讯;新建生产组B,无收到音讯.
• 提交生产偏移量

手动提交:客户端调用相应API 进行提交
主动提交:主动提交并不是通过定时工作去周期性的提交,而是在一些特定事件产生时触发进行提交
两种提交形式都是最终调用ConsumerCoorditor的同步提交和异步提交办法。能够通过设置enable.auto.commit属性来指定手动还是主动提交。
保障分布式事务
对于发送端,kafka外部有本人的重试次数配置,当重试次数达到,音讯还未发送胜利,咱们能够采取日志记录,后续通过弥补工作进行重试。
对于生产端,kafka是通过音讯偏移量来拉取音讯,所以对于broker发送给到生产端失败这个问题就不存在了。咱们探讨的只有在生产端接管到数据,而后生产失败的这种状况。生产失败如果咱们将偏移量重置,下次不就能够持续拉取了吗?但此计划会存在肯定的问题,kafka音讯在分区内都是程序读和程序写的,如果前一条音讯始终生产失败,那么会导致后续音讯生产不了,产生沉积问题。因而咱们仍然还是通过记录日志的形式,将生产失败的音讯记录,后续通过定时工作进行弥补,来保证数据的最终统一。