目录
3.4 CooperativeSticky 的解释【新的kafka中刚添加的策略】
1、消费者组和分区数之间的关系是怎样的?
消费者组是由多个消费者(consumer)组成的。形成一个消费者组的条件是:每个消费者的groupid都相同。消费者组中的每个消费者负责消费不同的分区的数据,一个分区只能由一个组内的一个消费者消费。消费者组之间相互独立。
所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
2、kafka如何知道哪个消费者消费哪个分区?
首先groupid对50进行取模,看最后的结果是哪个分区的节点,被选出来的这个分区的协调器就是本次消费者组的老大,消费者纷纷向该协调器进行注册,协调器从中随机选出消费者组中的Leader,然后把本次的消费情况发送给Leader,Leader制定完消费计划后,将该计划发送给协调器,协调器将消费计划公布,其他消费者按照消费计划进行消费。
3、kafka消费者的消费分区策略有哪些,默认是个?
Range、RoundRobin(轮询)、Sticky(粘性)、CooperativeSticky(配合的粘性)
默认策略是Range + CooperativeSticky
3.1 Range 以及再平衡
package com.bigdata.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallback {
public static void main(String[] args) throws InterruptedException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
"192.168.235.128:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<String, String>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 500; i++) {
// 添加回调
kafkaProducer.send(new ProducerRecord<>("first",
"bigdata " + i), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata,
Exception exception) {
if (exception == null) {
// 没有异常,输出信息到控制台
System.out.println(" 主题: " +
metadata.topic() + "->" + "分区:" + metadata.partition());
} else {
// 出现异常打印
exception.printStackTrace();
}
}
});
// 延迟一会会看到数据发往不同分区
Thread.sleep(20);
}
// 5. 关闭资源
kafkaProducer.close();
}
}
说明:Kafka 默认的分区分配策略就是 Range + CooperativeSticky,所以不需要修改策略。
默认是Range,但是在经过一次升级之后,会自动变为CooperativeSticky。这个是官方给出的解释。
默认的分配器是[RangeAssignor, CooperativeStickyAssignor],默认情况下将使用RangeAssignor,但允许通过一次滚动反弹升级到CooperativeStickyAssignor,该滚动反弹会将RangeAssignor从列表中删除。
()观看 3 个消费者分别消费哪些分区的数据。
假如消费情况和预想的不一样:
1、集群是否健康,比如某些kafka进程没启动
2、发送数据的时候7个分区没有使用完,因为它使用了粘性分区。如何让它发送给7个分区呢,代码中添加:
// 延迟一会会看到数据发往不同分区
Thread.sleep(20);
发现一个消费者消费了,5,6分区,一个消费了0,1,2分区,一个消费了3,4分区。
此时并没有修改分区策略,原因是默认是Range.
3)Range 分区分配再平衡案例
(1)停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内,越快越好)。
1 号消费者:消费到 3、4 号分区数据。
2 号消费者:消费到 5、6 号分区数据。
0号的数据,没人消费。
说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需
要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
(2)再次重新发送消息观看结果(45s 以后)。
1 号消费者:消费到 0、1、2、3 号分区数据。
2 号消费者:消费到 4、5、6 号分区数据。
说明:消费者 0 已经被踢出消费者组,所以重新按照 range 方式分配。
3.2 RoundRobin(轮询) 以及再平衡
1)RoundRobin 分区策略原理
2)RoundRobin 分区分配策略案例
(1)依次在 CustomConsumer、CustomConsumer1、CustomConsumer2 三个消费者代 码中修改分区分配策略为 RoundRobin。
package com.bigdata.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumerWithFenPei {
public static void main(String[] args) {
Properties properties = new Properties();
// 连接kafka
properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop11:9092");
// 字段反序列化 key 和 value
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
// 配置消费者组(组名任意起名) 必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test2");
// 指定分区的分配方案
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
// 消费者订阅主题,主题有数据就会拉取数据
// 指定消费的主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
// 一个消费者可以订阅多个主题
kafkaConsumer.subscribe(topics);
while(true){
//1 秒中向kafka拉取一批数据
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String,String> record :records) {
// 打印一条数据
System.out.println(record);
// 可以打印记录中的很多内容,比如 key value offset topic 等信息
System.out.println(record.value());
}
}
}
}
修改一下消费者组为test2
(2)重启 3 个消费者,重复发送消息的步骤,观看分区结果
3)RoundRobin 分区分配再平衡案例
(1)停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内,越快越好)。
1 号消费者:消费到 2、5 号分区数据
2 号消费者:消费到 4、1 号分区数据
0 号消费者 以前对应的数据没有人消费
说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
(2)再次重新发送消息观看结果(45s 以后)。
1 号消费者:消费到 0、2、4、6 号分区数据
2 号消费者:消费到 1、3、5 号分区数据
说明:消费者 0 已经被踢出消费者组,所以重新按照 RoundRobin 方式分配。
3.3 Sticky 以及再平衡
粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前, 考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销。 粘性分区是 Kafka 从 0.11.x 版本开始引入这种分配策略,首先会尽量均衡的放置分区 到消费者上面,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分区不变化。
比如分区有 0 1 2 3 4 5 6
消费者有 c1 c2 c3
c1 消费 3个 c2 消费2个 c3 消费2个分区
跟以前不一样的是,c1 消费的3个分区是随机的,不是按照 0 1 2 这样的顺序来的。
1)需求
设置主题为 first,7 个分区;准备 3 个消费者,采用粘性分区策略,并进行消费,观察
消费分配情况。然后再停止其中一个消费者,再次观察消费分配情况。
2)步骤
(1)修改分区分配策略为粘性。
注意:3 个消费者都应该注释掉,之后重启 3 个消费者,如果出现报错,全部停止等
会再重启,或者修改为全新的消费者组。
// 修改分区分配策略
ArrayList<String> startegys = new ArrayList<>();
startegys.add("org.apache.kafka.clients.consumer.StickyAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, startegys);
(2)使用同样的生产者发送 500 条消息。
可以看到会尽量保持分区的个数近似划分分区。
3)Sticky 分区分配再平衡案例
(1)停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内,越快越好)。
1 号消费者:消费到 2、5、3 号分区数据。
2 号消费者:消费到 4、6 号分区数据。
0 号消费者的任务没人顶替它消费
说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需
要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
(2)再次重新发送消息观看结果(45s 以后)。
1 号消费者:消费到 2、3、5 号分区数据。
2 号消费者:消费到 0、1、4、6 号分区数据。
说明:消费者 0 已经被踢出消费者组,所以重新按照粘性方式分配。
3.4 CooperativeSticky 的解释【新的kafka中刚添加的策略】
在消费过程中,会根据消费的偏移量情况进行重新再平衡,也就是粘性分区,运行过程中还会根据消费的实际情况重新分配消费者,直到平衡为止。
好处是:负载均衡,不好的地方是:多次平衡浪费性能。
动态平衡,在消费过程中,实施再平衡,而不是定下来,等某个消费者退出再平衡