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();
		}
	}
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值