kafka学习目录:kafka目录
我们向topic发送消息的时候是要把messages封装成一个ProducerRecord
对象的,源码如下:
可以看到再new一个ProducerRecord
对象时可分为三种情况:
org.apache.kafka.clients.producer.internals.DefaultPartitioner
中的 partition() 方法)说一个名词:Kafka再平衡机制。
所谓的再平衡,指的是在kafka consumer所订阅的topic发生变化时发生的一种分区重分配机制。
一般有三种情况会触发consumer的分区分配策略(再平衡机制):
test-*
此时如果有一个新建了一个topic test-user
,那么这个topic的所有分区也是会自动分配给当前的consumer的,此时就会发生再平衡;consumer的分区分配策略主要有三种:Round Robin
,Range
和Sticky
,默认使用的是Range
。这三种分配策略的主要区别在于:
Round Robin
:会采用轮询的方式将当前所有的分区依次分配给所有的consumer;Range
:首先会计算每个consumer可以消费的分区个数,然后按照顺序将指定个数范围的分区分配给各个consumer;Sticky
:这种分区策略是最新版本中新增的一种策略,其主要实现了两个目的:
Round Robin
和Range
分配策略实际上都会导致某几个consumer承载过多的分区,从而导致消费压力不均衡;在代码中:org.apache.kafka.clients.consumer.internals.AbstractPartitionAssignor
这个类默认有3种实现方式,如果要自定义分配策略的话,只需要继承AbstractPartitionAssignor这个类
Round Robin (轮询)策略针对是一个消费者组,根据组来分的。
roundronbin分配策略的具体实现是org.apache.kafka.clients.consumer.RoundRobinAssignor
意思就是一个group中的两个consumer同时订阅了两个topic,这两个topic都有3个分区,其中一个consumer被分配到了t0p0, t0p2, t1p1,另一个consumer被分配到了t0p1, t1p0, t1p2,那么为什么会这样分配呢?
这是因为轮询分配策略是基于所有可用的消费者和所有可用的分区的,与range策略最大的不同就是它不再局限于某个主题,如果所有的消费者实例的订阅都是相同的,那么这样最好了,可用统一分配,均衡分配。
假设,组中每个消费者订阅的主题不一样,分配过程仍然以轮询的方式考虑每个消费者实例,但是如果没有订阅主题,则跳过实例。当然,这样的话分配肯定不均衡。
什么意思呢?也就是说,消费者组是一个逻辑概念,同组意味着同一时刻分区只能被一个消费者实例消费,换句话说,同组意味着一个分区只能分配给组中的一个消费者。事实上,同组也可以不同订阅,这就是说虽然属于同一个组,但是它们订阅的主题可以是不一样的。
看个例子:
这里我们首先假设有三个topic:t0、t1和t2,这三个topic拥有的分区数分别为1、2和3,那么总共有六个分区,这六个分区分别为:t0-0、t1-0、t1-1、t2-0、t2-1和t2-2。这里假设我们有三个consumer:C0、C1和C2,它们订阅情况为:C0订阅t0,C1订阅t0和t1,C2订阅t0、t1和t2。那么这些分区的分配步骤如下:
首先将所有的partition和consumer按照字典序进行排序,所谓的字典序,就是按照其名称的字符串顺序,那么上面的六个分区和三个consumer排序之后分别为:
然后依次以按顺序轮询的方式将这六个分区分配给三个consumer,如果当前consumer没有订阅当前分区所在的topic,则轮询的判断下一个consumer:
尝试将t0-0分配给C0,由于C0订阅了t0,因而可以分配成功;
尝试将t1-0分配给C1,由于C1订阅了t1,因而可以分配成功;
尝试将t1-1分配给C2,由于C2订阅了t1,因而可以分配成功;
尝试将t2-0分配给C0,由于C0没有订阅t2,因而会轮询下一个consumer;
尝试将t2-0分配给C1,由于C1没有订阅t2,因而会轮询下一个consumer;
尝试将t2-0分配给C2,由于C2订阅了t2,因而可以分配成功;
同理由于t2-1和t2-2所在的topic都没有被C0和C1所订阅,因而都不会分配成功,最终都会分配给C2。
按照上述的步骤将所有的分区都分配完毕之后,最终分区的订阅情况如下:
从上面的步骤分析可以看出,轮询的策略就是简单的将所有的partition和consumer按照字典序进行排序之后,然后依次将partition分配给各个consumer,如果当前的consumer没有订阅当前的partition,那么就会轮询下一个consumer,直至最终将所有的分区都分配完毕。但是从上面的分配结果可以看出,(缺点:)轮询的方式在这种情况下会导致每个consumer所承载的分区数量不一致,从而导致各个consumer压力不均一。
Range策略是按照topic依次进行分配的(针对的是主题topic)。
range策略对应的实现类org.apache.kafka.clients.consumer.RangeAssignor
,同时这是默认的分配策略。
可以通过consumer配置 partition.assignment.strategy 参数来指定分配策略,它的值是类的全路径,是一个数组。
对于每个主题,我们以数字顺序排列可用分区,以字典顺序排列消费者。然后,将分区数量除以消费者总数,以确定分配给每个消费者的分区数量。如果没有平均划分(有余数),那么最初的几个消费者将有一个额外的分区。
简而言之,就是:
1、range分配策略针对的是主题(也就是说,这里所说的分区指的某个主题的分区,消费者指的是订阅这个主题的消费者组中的所有消费者)
2、首先,将分区按数字顺序排行序,消费者按消费者名称的字典序排好序,然后,用分区总数除以消费者总数。如果能够除尽,则皆大欢喜,平均分配
3、若除不尽,则位于排序前面的消费者将多负责一个分区。
举个例子:
我们假设有两个consumer:C0和C1,两个topic:t0和t1,这两个topic分别都有三个分区,那么总共的分区有六个:t0-0、t0-1、t0-2、t1-0、t1-1和t1-2。那么Range分配策略将会按照如下步骤进行分区的分配:
(缺点:)可以看到,如果按照Range
分区方式进行分配,其本质上是依次遍历每个topic,然后将这些topic的分区按照其所订阅的consumer数量进行平均的范围分配。这种方式从计算原理上就会导致排序在前面的consumer分配到更多的分区,从而导致各个consumer的压力不均衡。
Sticky
策略是Kafka0.11.x版本新增的策略,顾名思义,这种策略会保证再分配时已经分配过的分区尽量保证其能够继续由当前正在消费的consumer继续消费,当然,前提是每个consumer所分配的分区数量都大致相同,这样能够保证每个consumer消费压力比较均衡。
它主要有两个目的:
分区的分配要尽可能的均匀;
分区的分配尽可能的与上次分配的保持相同。
当两者发生冲突时,第一个目标优先于第二个目标。
关于这种分配方式的分配策略,我们分两种情况进行讲解,即初始状态的分配和某个consumer宕机时的分配情况。
初始状态分配的特点是,所有的分区都还未分配到任意一个consumer上。这里我们假设有三个consumer:C0、C1和C2,三个topic:t0、t1和t2,这三个topic分别有1、2和3个分区,那么总共的分区为:t0-0、t1-0、t1-1、t2-0、t2-1和t2-2。关于订阅情况,这里C0订阅了t0,C1订阅了t0和1,C2则订阅了t0、t1和t2。这里的分区分配规则如下:
上面的分配过程中,需要始终注意的是,虽然示例中的consumer顺序始终没有变化,但这是由于各个分区分配之后正好每个consumer所分配的分区数量的排序结果与初始状态一致。这里读者也可以比较一下这种分配方式与前面讲解的Round Robin
进行对比,可以很明显的发现,Sticky
重分配策略分配得更加均匀一些。
由于前一个示例中最终的分区分配方式模拟宕机的情形比较简单,因而我们使用另一种订阅策略。这里我们的示例的consumer有三个:C0、C1和C2,topic有四个:t0、t1、t2和t3,每个topic都有两个分区,那么总的分区有:t0-0、t0-1、t1-0、t1-1、t2-0、t2-1、t3-0和t3-1。这里的订阅情况为三个consumer订阅所有的主题,那么如果按照Sticky
的分区分配策略,初始状态时,分配情况如下,读者可以按照前一示例讲解的方式进行推算:
这里我们假设在消费的过程中,C1发生了宕机,此时就会发生再平衡,而根据Sticky
策略,其再分配步骤如下:
在上面的分区分配过程中,我们可以看到,由于分区的不断分配,各个consumer所拥有的分区数量也在不断变化,因而其排序情况也在变化,但是最终可以看到,各个分区是均匀的分配到各个consumer的,并且还保证了当前consumer已经消费的分区是不会分配到其他的consumer上的。