一、什么是消费者组、分区

在Kafka中,同一主题下,支持“消费者组”的概念,即一个消息可以被不同的消费者消费,一个消息只能被同组内一个消费者消费

 

        

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据文件

 

 

 

 分区:partition,一个Topic有多个分区,分区内的消息是有序的。(一个分区只属于单个主题,同一主题下的不同分区包含的消息不同)

  一个分区只能同时被同消费者组的一个消费者消费

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据_02

在分区中还有一个概念:offset,offset是消息在分区中的唯一标识,消息被追加到分区日志文件的时候都会分配一个特定的偏移量

 

 

(问)Kafka如何保证消息的全局有序?

 全局使用一个生产者,全局使用一个消费者,全局使用一个分区

 

 

从使用层面来看,Kafka的高性能体现在:

1、多分区并行消费

  2、数据压缩+消息批量发送,节省网络IO开销

 

 

partition的数量无上限,但并不意味着越多越好

 1、越多的分区可以提供更高的吞吐量

需要打开更多的文件句柄

导致消费者消费的延迟越高

需要更多的内存

更长时间的恢复期

 

二、Kafka的分区策略

分区策略:即“该消息要发送给哪个分区”

 1.指定分区号【场景很少】

 2.根据分区Key值Hash

   

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据_03

 

 3.轮询发送【消息无需有序的场景,如审计信息】

 

 

 4.自定义分区策略(实现PartitionAssignor接口)

 

 

 

三、Kafka的Producer配置

SpringBoot集成Kafka生产者配置文件实例

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_04

 

Producer配置之request.required.asks

  0:相当于异步的,不需要leader给予回复,producer立即返回,发送就是成功。那么发送消息网络超时或broker崩溃,既有可能丢失也可能会重发。

  1:当leader接收到消息之后发送ack,失败会重试,丢的概率很小。

大部分的ISR的follower都同步消息成功后发送ack,丢失消息可能性比较低。(与后面配置的ISR数量有关)

 

 

四、Kafka的Consumer

消息消费方式:

 1、PUSH:消息队列主动地将消息推送给消费者

 2、PULL(Kafka消费方式):由消费者客户端主动向消息队列拉去消息

 

PUSH方式:消息实时性高,但没有考虑客户端的消费能力

PULL方式:消息实时性低,可能造成大量无效请求

 

 

消费者Kafka基础配置【以SpringBoot集成Kafka为例】

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_05

 

消费者Kafka其他配置【以SpringBoot集成Kafka为例】

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_06

enable-auto-commit:是否开启自动提交

auto-offset-reset:从哪里开始消费

 

五、分区分配策略

分区分配策略:即“此分区内的消息应该由谁来消费”?【同一个分区只能被同组的一个消费者消费】

 

一、RangeAssignor[默认]: Range

说明:对同一个 topic 里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。通过 partitions数/consumer数 来决定每个消费者应该消费几个分区。如果除不尽,那么前面几个消费者将会多  消费 1 个分区。

弊端:多topic时,负载不均衡。前面的Consumer分配的分区过多

 

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据_07

 

 

 

二、RoundRobinAssignor:Round Robin【大多数场景此策略可以满足】

说明:把所有的 partition 和所有的 consumer 都列出来,然后按照 hashcode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。

弊端:当不是适合的场景情况下,分配不完美,当且仅当如下情况时才均匀:

    ① 每个消费者订阅的主题,必须是相同的 

    ② 每个主题的消费者实例都是相同的   

 

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_08

 

三、StickyAssignor:粘性策略

说明:分区的分配要尽可能的均匀;分区的分配尽可能的与上次分配的保持相同。 当两者发生冲突时,第一个目标优先于第二个目标。

 

 

 

Kafka Rebalance机制:即“分区重新分配”

触发时机:【变量:Topic数、分区数、消费者数】

1、组成员个数发生变化(有新的 consumer 实例加入该消费组或者离开组)

  服务滚动部署(来回启动、关闭)

  服务宕机重启

  Consumer性能不足,误判离线

2、订阅 Topic 的分区数发生变化。

  分区扩容

3、订阅的 Topic 个数发生变化。

 

 

Kafka Rebalance过程(以Eager Rebalance Protocol为例):

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_09

 

简单描述:(当上述触发时机发生时触发此过程)

1、GroupCoordinator得知需要触发Rebalance,拒绝所有消费者的心跳同时返回[Rebalance]事件。

2、消费者不再心跳,而是向GroupCoordinator发送JoinGroupRequest请求。

3、GroupCoordinator(组协调器) 从 Consumer Group 选出 Leader。

4、由Leader根据分区分配策略,计算消费关系,将结果返回给GroupCoordinator。

5、GroupCoordinator将结果通知到各个Consumer。

重点:Rebalance过程中,所有消费者停止消费,应当尽量避免Rebalance的发生

 

如何判断Consumer存活还是假死?

假死判断:消费者有心跳,但是不干活

存活判断:心跳(定时发请求)

 

Broker与Consumer存活的双向检测:

 

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据_10

心跳检测相关参数:

Broker端参数:

  group.min.session.timeout.ms:已注册的消费者允许的会话超时的最小时长,小于便拒绝此心跳请求。(防止频繁接收心跳请求,影响效率)

  group.max.session.timeout.ms:已注册的消费者允许的会话超时的最大时长,大于则认为是消费者离线。

Consumer端参数:

  session.timout.ms: Consumer session 过期时间,距离上次心跳成功的时间,超过此时间,会认为Broker不可用。【判断Broker不可用】

    Consumer的session.timout.ms介于Broker的min与max之间

  heartbeat.interval.ms:心跳间隔。

    通常设置的值要低于session.timeout.ms的1/3  (可能消费者突然性能下降,无法及时消费,但是并没有下线,设置1/3,就有两次重新发送心跳请求的时间)

 

数据拉取线程相关参数:

  max.poll.interval.ms:长时间没有调用pull,且间隔超过这个值时,consumer判定自身为假死,自动离线。【判断自身不可用】

思考:从max.poll.interval.ms参数来看,如何减少Rebalance的发生?      一次业务处理时长不得超过 max.poll.interval.ms

 

 

 

六、Kafka消息存储机制

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_11

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据_12

 

Kafka 消息是采用 Segment 的存储方式进行存储的,每个分区文件夹下拆分为N个小文件,共同存储修改分区中的所有消息。

一个日志段,有3个文件:

  数据文件(Data File):存数据,新消息追加写在后面

  数据文件分段:segment

  索引文件(Index File):采用稀疏索引,每隔一定字节的数据建立一条索引,这样的话就是避免了索引文件占用过多的空间和资源,从而可以将索引文件保留到内存中。缺点是没有建立索引的数据在查询的过程中需要小范围内的顺序扫描操作。

 

索引文件如何使用?比如:要查找绝对offset为7的Message:

1、用二分查找确定它是在哪个LogSegment中,自然是在第一个Segment中。

2、打开这个Segment的index文件,也是用二分查找找到offset小于或者等于指定offset的索引条目中最大的那个offset。自然offset为6的那个索引是我们要找的,通过索引文件我们知道offset为6的Message在数据文件中的位置为9807。

3、打开数据文件,从位置为9807的那个地方开始顺序扫描直到找到offset为7的那条Message。

 

Kafka的高性能还体现在它的存储机制上,因为Kafka消息的存储采用了稀疏索引,写数据不需要每次都写索引文件,提高性能

分段存储的好处? 

   1、读取效率高 

   2、方便消息清理,仅删除,无修改

 

Kafka 中每个分区是一个有序的,不可变的消息序列,新的消息不断追加到 partition 的末尾,这个就是顺序写。

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据_13

 

磁盘完成一个I/O请求所花费的时间 = 寻道时间 + 旋转延迟 + 数据传输时间

连续 I/O与随机I/O

  连续I/O:指的是本次 I/O 给出的初始扇区地址和上一次 I/O 的结束扇区地址是完全连续或者相隔不多的。

  随机I/O:反之,如果相差很大,则算作一次随机 I/O。

连续 I/O 比随机 I/O 效率高的原因是:在做连续 I/O 的时候,磁头几乎不用换道,或者换道的时间很短 顺序写使硬盘尽可能的发生连续I/O,而不是随机I/O

 

从硬件层面来看,Kafka的高性能体现在它的顺序写,因为Kafka写消息是顺序写,这样使硬盘尽可能的发生连续I/O

 

 

操作系统层面-零拷贝

 

零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。

mmap:将内核中读缓冲区(read buffer)的地址与用户空间的缓冲区(user buffer)进行映射。从而实现内核缓冲区与应用程序内存的共享,省去了将数据从内核读缓冲区(read buffer)拷贝到用户    缓冲区(user buffer)的过程。

sendfile:数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer,无需 CPU 拷贝。

 

不使用零拷贝一次文件写入的过程:

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据文件_14

 

 

零拷贝技术(mmap)下的写数据:

  

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_15

                                        

 零拷贝技术(sendfile)下的读数据:

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_16

 

从操作系统层面来看,Kafka的高性能体现在使用了零拷贝技术

 

七、Kafka的数据可靠性原理

副本机制

 

 副本:通常是指分布式系统在多台网络互联的机器上保存有相同的数据拷贝

好处:

  1、提供数据冗余 系统部分组件失效,系统依然能够继续运转,因而增加了整体可用性以及数据持久性

  2、提供高伸缩性(需要支持读写分离) 支持横向扩展,能够通过增加机器的方式来提升读性能,进而提高读操作吞吐量。

  3、改善数据局部性(需要支持读写分离) 允许将数据放入与用户地理位置相近的地方,从而降低系统延时。

 

备注:Kafka不支持读写分离

    基于领导者(Leader-based)的副本机制              有 3 台 Broker 的 Kafka 集群上的副本分布情况如下:

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据文件_17

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_数据文件_18

 

常见的副本复制数据方式:[commit指broker返回客户端成功,commit前客户端请求阻塞]

  同步复制:只有所有的follower把数据拿过去后才commit。

  异步复制:只要leader拿到数据立即commit,等follower慢慢去复制。

半同步复制:折中方案,有一部分follower同步完成,便commit。【推荐】

一部分follower + leader 称之为ISR(In-sync Replicas,同步中的副本)

 

 

ISR:In-sync Replicas,一种同步复制、异步复制的折中方案。即一种在性能与数据可靠性的折中方案。 指的是同步中的副本(含Leader),一个动态调整的集合,而非静态不变的。

ISR不但代表一种机制,ISR同时也代表了一个集合(同步中的副本)

  AR:Assigned Repllicas,分区的所有副本集合

  ISR:In-Sync Replicas,所有与leader副本保持一定程度同步的副本(包括Leader)集合

  OSR:Out-Sync Relipcas,与leader副本同步滞后过多的follow副本的集合

AR = ISR + OSR

 

折中的副本数如何配置? (即“几个副本同步完,算数据可靠?”)

  Topic配置项:min.insync.replicas=2【需要保证ISR中至少有多少个replica】

 

不满足min.insync.replicas会怎么样?

  当Producer配置acks=-1时,生产者会抛出分区不可用异常,不允许写入(因为写入会丢) 【使用此配置时,ISR有效】

  当Producer设置acks=1/0时,生产者不受影响【使用此配置时,ISR没用】

 

“动态调整,非静止不变”如何理解?

Broker配置:replica.lag.time.max.ms=10000 [最长容忍的不拉取数据的时间]

follower超过10秒没有向leader发起fech请求:从ISR剔除(follower可能在fullGC、资源紧张等)

follower超过10秒又向leader发起fech请求:添加到ISR中

 

副本全部宕机,恢复策略:

  1、等待ISR中的任一个Replica“活”过来,并且选它作为Leader

  2、选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader

Topic配置:unclean.leader.election.enable=true代表第二种,fasle代表第一种

  v0.11默认false,之前默认true

如何选择恢复策略?

 根据实际业务来选择,如果对实时消息敏感的业务,选择第一种,因为ISR中follower的消息最接近leader中的消息,如果对实时消息不敏感,就使用第二种,可以提高性能。

 

LEO(last end offset):日志末端偏移量,记录了该副本对象底层日志文件中下一条消息的offset值。

HW(highwatermark):高水位值,ISR中最小的LEO为HW。

 

 

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_19

 

 

 

灰色-已提交:小于HW值的所有消息被认为是“已备份的”,对consumer可见。

白色-未提交:大于等于HW值所有消息被认为是“未备份的”,对consumer不可见。

 

 

 

 

Java kafka多个消费者需要重复消费一个topic kafka 多消费者消费同一个topic_零拷贝_20

 

背景:生产者acks=-1,Topic的min.insync.replicas=2,此时ISR为<leader、follower1、follwer2>

异常情况:ISR中的follwer1完成同步m2,follower2未完成同步m2,此时leader宕机。

问:此时原follower2接管成为Leader,会导致什么问题?

因为此时的HW为follower2的m1,若follower2接管为leader,则会导致follower1的m2消息无法被消费

 

八、Kafka的高可用原理

分区副本Leader选举原理

  各自为政”的leader选举(v0.8.2-)

    说明:每个Partition的多个Replica同时竞争Leader 原理:抢占创建Zookeeper的临时节点,抢占成功则为Leader,否则注册watch;若Leader宕机,触发watch,再次抢占。

    优点:实现简单

    缺点: 1、性能差(Zookeeper不适合频繁写) 2、Herd Effect(羊群效应)

 

 

 

  基于controller的leader选举(v0.8.2+)

    说明:整个集群中选举出一个Broker作为Controller,Controller为所有Partition指定Leader及Follower。

    优点:减轻Zookeeper负载

    缺点:引入Controller增加了复杂度,需要考虑Controller的Failover

 

 

 

 

Controller: 控制器,Apache Kafka的核心组件,在Apache Zookeeper的帮助下管理和协调控制整个Kafka集群。

  集群中的任意一台Broker都能充当Controller的角色。

  每个正常运行的Kafka集群,在任何时刻都有且只有一个Controller。

 选举方式:Broker 在启动时,会尝试去 ZooKeeper 中创建 /controller 节点。第一个成功创建 /controller 节点的 Broker 会被指定为控制器。

 相关ZK节点

  /controller:临时节点,记录当前的Controller的brokerId以及选举时间

  /controller_epoch:持久节点,记录控制器发生变更的次数,即记录当前的控制器是第几代控制器。

controller_epoch的作用?  防止"脑裂"

 

背景:若Controller所在的Broker故障,则必须有新Controller继续工作,但很难确定原Broker是宕机还是只是暂时的故障,若原Broker仅是假死,假死后,在原Controller眼里没有任何变化,Broker甚至  不知道自己已经“下岗”了,此场景在分布式系统中,称之为“脑裂”

解决办法:当其他Broker知道当前的epoch number时,如果他们从Controller收到包含旧(较小)epoch number的消息,则它们将被忽略。

 

——面试题——

 

1、Kafka高性能的原理?

     从使用层面看,Kafka是多分区并行消费,同时压缩数据和消息批量发送,节省了网络IO开销

  从存储机制层面看,Kafka采用稀疏索引来写数据,故不需要每次都写索引文件;采用了分段存储,读取效率高,同时方便消息清理

  从硬件层面看,Kafka采用了顺序写的方式,将消息追加到分区的末尾,顺序写使硬盘尽可能的发生连续IO,而不是随机IO

    从操作系统层面看,Kafka采用了零拷贝技术,加快了读写数据的速度。

 

2、Kafka的应用场景?

 异步处理、应用解耦、流量削峰、冗余数据、消息通讯