深入理解Kafka(三):生产者分区策略源码剖析

前言

我们都知道Kafka中的topic和分区(partition)的概念,一个topic可以有一个或多个分区(partition),消息数据都是存储在分区(partition)中的,生产者(Producer)发送消息到topic,消费者(Consumer)从指定的topic中消费消息,但是生产者的消息是投递到topic下的哪个分区(partition)的?
本文通过解读源码分析生产者(Producer)投递消息的分区策略

分区数(partition)设置

分区数有两种设置方式:

  1. 通过server.properties配置文件:
num.partitions=1 # 默认为1
  1. 通过kafka自带命令,这个方法修改的分区数要大于原来的分区数,否则不能修改:
bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic topic-default --partitions 5

KafkaTemplate的send方法

KafkaTemplate类的send方法如下:

	public ListenableFuture<SendResult<K, V>> send(String topic, @Nullable V data) {
        ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, data);
        return this.doSend(producerRecord);
    }

    public ListenableFuture<SendResult<K, V>> send(String topic, K key, @Nullable V data) {
        ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, key, data);
        return this.doSend(producerRecord);
    }

    public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, @Nullable V data) {
        ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, partition, key, data);
        return this.doSend(producerRecord);
    }

    public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, @Nullable V data) {
        ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, partition, timestamp, key, data);
        return this.doSend(producerRecord);
    }

由此可见,生产者可以指定分区发送消息,继续深入代码来到KafkaProducer类的doSend()方法

	private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) {
        ... //省略部分代码
		int partition = this.partition(record, serializedKey, serializedValue, cluster);
		... //省略部分代码
    }

如果指定了分区,则直接发送消息到该分区

	private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {
        Integer partition = record.partition();
        return partition != null ? partition : this.partitioner.partition(record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
    }

如果指定的分区不存在则会抛出TimeoutException

	try {
        this.metadata.awaitUpdate(version, remainingWaitMs);
     } catch (TimeoutException var15) {
         throw new TimeoutException(String.format("Topic %s not present in metadata after %d ms.", topic, maxWaitMs));
     }

代码this.partitioner.partition(),其中this.partitioner默认实现是DefaultPartitioner,查看DefaultPartitioner的实现方法partition():

	private final ConcurrentMap<String, AtomicInteger> topicCounterMap = new ConcurrentHashMap(); // 自增计数器
	public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        if (keyBytes == null) { // key不存在
            int nextValue = this.nextValue(topic); // 每次发送消息,计数器自增1
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size(); // 自增后的数字和分区数取模
                return ((PartitionInfo)availablePartitions.get(part)).partition();
            } else {
                return Utils.toPositive(nextValue) % numPartitions;  // 自增后的数字和分区数取模
            }
        } else {
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions; // murmur2哈希算法,结果和分区数取模
        }
    }
    /**
     * 计数器自增
     */
	private int nextValue(String topic) {
        AtomicInteger counter = (AtomicInteger)this.topicCounterMap.get(topic);
        if (null == counter) {
            counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
            AtomicInteger currentCounter = (AtomicInteger)this.topicCounterMap.putIfAbsent(topic, counter);
            if (currentCounter != null) {
                counter = currentCounter;
            }
        }
        return counter.getAndIncrement();
    }

因此,默认的分区策略可总结为如下:

  • 如果在发消息的时候指定了分区,则消息投递到指定的分区
  • 如果不指定分区,但是指定了key,则基于key的murmur2哈希值与分区数取模来选择分区
  • 如果既不指定分区,且不指定key,则每次发送消息自增1后的数字与分区数取模来选择分区
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值