文章目录
- kafka分区分配策略
- 分区分配策略
- 代码测试
kafka分区分配策略
分区分配策略
- 默认是range strategy策略
- Kafka从0.11.x版本开始有3种方式:RangeAssignor、RoundRobinAssignor、StickyAssignor(之前是前2种)
- 3种策略解释
- 1.RangeAssignor:
- 假设n=分区数/消费者数量,m=分区数%消费者数量,那么前m个消费者每个分配n+1个分区,后面的(消费者数量-m)个消费者每个分配n个分区。
- 排完序的分区将会是0, 1, 2, 3, 4, 5, 6, 7, 8, 9;消费者线程排完序将会是C1-0, C2-0, C2-1。
C1-0 将消费 0, 1, 2, 3 分区
C2-0 将消费 4, 5, 6 分区
C2-1 将消费 7, 8, 9 分区 - 弊端
假如我们有2个主题(T1和T2),分别有10个分区,那么最后分区分配的结果看起来是这样的:
C1-0 将消费 T1主题的 0, 1, 2, 3 分区以及 T2主题的 0, 1, 2, 3分区
C2-0 将消费 T1主题的 4, 5, 6 分区以及 T2主题的 4, 5, 6分区
C2-1 将消费 T1主题的 7, 8, 9 分区以及 T2主题的 7, 8, 9分区
可以看出,C1-0 消费者线程比其他消费者线程多消费了2个分区,这就是Range strategy的一个很明显的弊端。
- 2.RoundRobinAssignor:
- 轮训
- 把主题和分区组成topicAndPartition列表,再把列表按照hashcode排序,轮询分配给消费者。
加入按照 hashCode 排序完的topic-partitions组依次为T1-5, T1-3, T1-0, T1-8, T1-2, T1-1, T1-4, T1-7, T1-6, T1-9,我们的消费者线程排序为C1-0, C1-1, C2-0, C2-1,最后分区分配的结果为:
C1-0 将消费 T1-5, T1-2, T1-6 分区;
C1-1 将消费 T1-3, T1-1, T1-9 分区;
C2-0 将消费 T1-0, T1-4 分区;
C2-1 将消费 T1-8, T1-7 分区; - 弊端
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
不是最优解(不均衡),t1p1给C1才是最优的
- 3.StickyAssignor(0.11.x版本开始引入):
- 特点1:轮训(组内消费者消费同样主题, 和RoundRobinAssignor类似
假设消费组内有3个消费者:C0、C1和C2,它们都订阅了4个主题:t0、t1、t2、t3,并且每个主题有2个分区,也就是说整个消费组订阅了t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1这8个分区
消费者C0:t0p0、t1p1、t3p0
消费者C1:t0p1、t2p0、t3p1
消费者C2:t1p0、t2p1
- 特点2:最优配置(消费者和分区数对应不均衡时体现)
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
消除了上面RoundRobinAssignor的弊端
消费者C0:t0p0
消费者C1:t1p0、t1p1
消费者C2:t2p0、t2p1、t2p2
- 特点3:分配尽可能的与上次分配的保持相同(一个消费者挂了重新分区体现)
- 针对上面 ”特点1“ 中情况,C1挂了,重新分配,RoundRobinAssignor 和 StickyAssignor对比:
消费者C0:t0p0、t1p0、t2p0、t3p0
消费者C2:t0p1、t1p1、t2p1、t3p1
原有的保持不变,挂的开始轮训分给C0、C2
消费者C0:t0p0、t1p1、t3p0、t2p0
消费者C2:t1p0、t2p1、t0p1、t3p1
- 针对上面 ”特点2“ 中情况,C1挂了,重新分配,RoundRobinAssignor 和 StickyAssignor对比:
消费者C1:t0p0、t1p1
消费者C2:t1p0、t2p0、t2p1、t2p2
原有的保持不变,采用最佳分配(比另外两者分配策略而言显得更加的优异)
消费者C1:t1p0、t1p1、t0p0
消费者C2:t2p0、t2p1、t2p2
代码测试
- pom
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>1.1.1</version>
</dependency>
- scala
package zz.kafka;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
/**
* @description:
* @author: zz
* @create: 2020/07/07
*/
public class Demo2 implements Runnable {
public void run() {
try {
// System.out.println(Thread.currentThread().getName());
// Thread.sleep(5000);
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "false");
// props.put("partition.assignment.strategy", "org.apache.kafka.clients.consumer.RangeAssignor");
// props.put("partition.assignment.strategy", "org.apache.kafka.clients.consumer.RoundRobinAssignor");
props.put("partition.assignment.strategy", "org.apache.kafka.clients.consumer.StickyAssignor");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// consumer.subscribe(Arrays.asList("test", "test1"));
// consumer.subscribe(Arrays.asList("test1"));
if (Thread.currentThread().getName().equals("Thread-0")) {
consumer.subscribe(Arrays.asList("test"));
}else if(Thread.currentThread().getName().equals("Thread-1")) {
consumer.subscribe(Arrays.asList("test1"));
} else {
consumer.subscribe(Arrays.asList("test", "test1"));
}
final int minBatchSize = 200;
List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.topic() + " # " + record.partition() + " # " + Thread.currentThread().getName());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new Demo2());
thread.start();
}
}
}