本文主要用来整理kafka消费者相关的一些知识点并加以自我理解,主要参考自《深入理解kafka核心设计与实践原理》--朱忠华
应用程序主要就是通过KafkaConsumer来订阅主题,并从订阅的主题中拉取消息。使用KafkaConsumer之前先要了解消费组和消费者之间的概念关系;
1、消费者和消费组
消费者Cosumer负责订阅主题topic,并从订阅的主题中拉去消息。与其它消息中间件不同的是:Kakfa的消费者Consumer和主题之间还有一层消费组(Consumer Group),每个消费者都有一个对应的消费组,主题中的每条消息只能被订阅它的消费组下的一个消费者拉取消费;
例如某个主题下有4个分区:P0,P1,P2,P3,有两个消费组A group和B group 都订阅了这个主题;消费组A中有4个消费者(C0,C1,C2,C3),消费组B中有两个消费者(C4,C5);按照kafka默认的分配规则,主题下的每个分区只能被同一个消费组中的一个消费组所消费,最终的分配结果将是A中的每个消费者分配到一个分区,B中的每个消费者分配到两个分区;如下图:
再看下组内消费者个数变化时所对应的分区分配的演变。当上述的消费组A中的消费者变为3个只剩C0、C1、C2时,对应的就会有一个消费者就会分配到两个分区;
当A消费组中的消费者个数变为5个添加C6,那么由于C0~C3已经分配了所有分区,所有C6就分配不到任何分区了,也无法消费任何消息;
综上,消费者、消费组这种模型让整体的消费能力具备横向伸缩性,我们可以通过增加(或减少)消费者的个数来提高(或降低)整体的消费能力。但是,对于分区数固定的情况,一味的增加消费者个数不会让消费能力一直得到提升;如果消费者个数大于分区数,就会有上面C6消费无法分配到任何分区的情况;上面提到的分配逻辑都是按照默认的分区分配策略进行分析的,我们可以通过消费者客户端参数指定消费者与订阅主题之间的分区分配策略,参数partition.assignment.strategy(细节再论)。
对于消息中间间而言,一般有两种消息投递模式:点对点(P2P,Point-to-Point)模式和发布/订阅(Pub/Sub)模式。点对点是基于队列的,消息生产者向对列发送消息,消息消费者从队列中接收消息,队列中的每条消息只能被一个消费者消费;发布订阅模式定义了如何向一个内容节点发布和订阅消息,这个内容节点就是topic主题。主题是消息传递的中介,使得消息的订阅者和发布者相互独立,不需要接触就可以进行消息传递;发布订阅模式下的消息可以被多个订阅者订阅消费;kafka同时支持两种消息投递模式,这是得益于消费者、消费组模式;
- 如果消费者隶属于一个消费组,那么所有消息都会被均衡的投递到每一个消费者,即每条消息只能被一个消费者消费,这就相当于点对点模式;
- 如果每个消费者隶属于不同的消费组,那么消息可以会被广播给消费组的消费者,即每条消息会被所有的消费者处理,这就相当于发布订阅模式;
切入正题消费者客户端相关:
1、配置消费者客户端参数及创建相应的消费者实例
KafKaConsumer<String,String> cosumer=new KafkaConsumer<>(props); //根据properties属性创建消费者实例
创建消费者实例需要配置相应的参数,其中有4个必填参数:
- bootstrap.server:与生产者客户端KafkaProducer中的相同,用来指定kafka集群中所需的broker地址清单,形式为host1:port1,host2:port2,中间逗号隔开;要注意的是这里并不需要配置集群中所有broker的地址,消费者会从现有的配置中获取到全部的kafka集群成员。一般设置两个以上,当任意一个宕机的时候,仍然可以连接到kafka集群上。
- group.id:消费者隶属的消费组;如果为空,会爆出Exception in thread "main" org.apache.kafka.common.errors.InvalidGroupIdException:The configured gourpId is invalid;
- key.deserializer和value.deserialzer :与生产者客户端中的序列化器对应的反序列化器;
kafka还有众多其他的参数,我们在构造的KafkaConsumer的时候可以根据实际情况去灵活调配;
- auto.offset.reset:自动提交偏移量
2、订阅主题和分区
一个消费者可以订阅一个或多个主题;我们一般使用subscribe()方法订阅主题,对于这个方法而言,可以以集合的形式订阅主题,也可以以正则表达式的形式订阅特定模式的主题。subscribe提供的重载方法如下:
public void subscribe(Collection<String> topics,ConsumerRebalanceListener listener);//订阅主题集合,切进行再均衡
public void subscribe(Collection<String> topics); //订阅主题集合
public void subscribe(Pattern pattern,ConsumerRebalanceListener listener); //订阅特定模式主题,且进行再均衡
public void subscribe(Pattern pattern);//订阅特定模式主题
3、分区分配策略
kafka提供了消费者客户端参数partition.assigment.strategy来设置消费者与订阅主题之间的分区分配策略。默认情况下此数值为org.apache.kafka.clients.consumer.RangeAssignor,即采用RangeAssignor分配策略。除此之外,kafka还提供了另外两种分配策略:RoundRobinAssignor和StrickyAssignor。消费者客户端参数partition.assigment.strategy可以配置多个分配策略,彼此之间以逗号隔开;
3.1、RangeAssignor分配策略
RangeAssignor分配策略原理按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进平均分配,以保证分区尽可能均匀的分配给所有的消费者;对于每一个主题RangeAssignor策略会将消费组内所有订阅这个主题的消费者按照名称的字典排序,然后为每个消费者划分固定的分区范围,如果不够平局分配,那么字典序靠前的消费者会被多分配一个分区。
假设n=分区数/消费者数量,m=分区数%消费者数量,那么前m个消费者每个分配n+1个分区,后面的(消费者数量-m)个消费者每个分配n个分区。
消费组内有两个消费者C0和C1,都订阅了主题t0和t1,并且每个主题都有4个分区,那么订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t0p3、t1p0、t1p1、t1p2、t1p3;
最终分配结果为:消费者C0: t0p0、t0p1、t1p0 、t1p1 消费者C1:t0p2、t0p3、t1p2、t1p3
这样分配很均匀;但假设上面2个主题只有3个分区,那么订阅的所有分区可以标识为t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终分配结果为:消费者C0: t0p0、t0p1、t1p0、t1p1 消费者C1:t0p2、t1p2
由此可见这样的分配并不均匀,如果将类似情形扩大有可能出现消费者过载的情况;
3.2、RoundRobin分配策略
原理是将消费组内所有消费者及消费者订阅的所有主题分区按照字典排序,然后通过轮询方式逐个将分区依次分配给每个消费者。客户端参数partition.assigment.strategy参数值为org.apache.kafka.clients.consumer.RoundRobinAssignor。
如果同一组内所有消费者的订阅信息都是相同的,那么RoundRobinAssignor分配策略的分区分配会是均匀的。举个例子消费组内2个消费者C0、C1都订阅主题t0和t1并且每个主题有3个分区,那么订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2;最终结果为:消费者C0—— t0p0、t0p2、t1p1 消费者C1——t0p1、t1p0、t1p2
如果同一个消费组内的消费者订阅的信息是不相同的,那么在执行分区分配的时候就不是完全轮询分配,有可能导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个主题,那么分配分区的时候此消费者将分配不到这个主题的任何分区。
假设消费组内有3个消费者(C0、C1和C2),他们共同订阅了3个主题(t0\t1\t2),这3个主题分别有1个、2个、3个分区,即整个消费组订阅了t0p0、t1p0、t1p1、t2p0、t2p1、t2p2这6个分区。具体而言,消费者C0订阅的是主题t0,消费者C1订阅的主题是t0和t1,消费者C2订阅的是主题t0、t1和t2,那么最终分配结果为:
消费者C0—t0p0 消费者C1——t1p0 消费者C2—— t1p1、t2p0、t2p1、t2p2
可见RoundRobinAssignor也不是最优解
3.3、StickyAssignor分配策略
sticky翻译是“黏性的”,kafka的0.11.x引入这种分配策略;它的设计主要围绕两个目的:
(1)、分区的分配要尽可能的均匀
(2)、分区的分配尽可能与上次的分配的保持相同
当两者发生冲突时第一个目标优于第二个目标。鉴于这两个目标,该策略比前两个策略要复杂的多。
3.4、自定义分区分配策略
除了kafka提供的3种分区分配策略,还可以自定义分区分配策略来实现更多的可选功能。自定义分配策略必须要实现org.apache.kafka.clients.consumer.internals.PartitionAssignor接口
来源:CSDN
作者:a1290123825
链接:https://blog.csdn.net/a1290123825/article/details/100069568