Kafka-我发的消息都发到哪儿去了?

如题:我发的消息都发到哪儿去了?你是否有此疑问?

1、首先,我们先来看看kafka的消息格式是什么样的。

public class ProducerRecord<K, V> {

     /**
     * 消息主题
     */
    private final String topic;
    /**
     * 指定的分区号
     */
    private final Integer partition;
    /**
     * 消息头信息
     */
    private final Headers headers;
    /**
     * 消息的key
     */
    private final K key;
    /**
     * 消息值
     */
    private final V value;
    /**
     * 时间戳
     */
    private final Long timestamp;

}

 

2、了解完Kafka消息的格式,我们就来看看kafka是如何发送的吧(暂不考虑事务消息)。

    /**
     * See {@link KafkaProducer#send(ProducerRecord)}
     */
    Future<RecordMetadata> send(ProducerRecord<K, V> record);

    /**
     * See {@link KafkaProducer#send(ProducerRecord, Callback)}
     */
    Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback);

KafkaProducer中做了重载,send(producerRecord)内部调用的是send(producerRecord,callback),如下图:

    /**
     * Asynchronously send a record to a topic. Equivalent to <code>send(record, null) 
       </code>.
     * See {@link #send(ProducerRecord, Callback)} for details.
     */
    @Override
    public Future<RecordMetadata> send(ProducerRecord<K, V> record) {
        return send(record, null);
    }


3、知道如何发送之后我们就来看看真正发送的地方吧;

发送主要做了几件事情:

  1. 检查生产者是否已关闭
  2. 通过发送消息中的主题和分区找到集群信息
  3. 对key和value进行序列化
  4. 计算消息应该发到哪个分区上
  5. 找到发送到哪个分区之后就准备发送
  6. 发送失败捕获异常并进行发送异常回调

4、那么这个分区到底是如何计算的呢?如下为生产者中的代码@Producer#832行(kafka 2.0版本)

            byte[] serializedKey;
            try {
                serializedKey = keySerializer.serialize(record.topic(), record.headers(), record.key());
            } catch (ClassCastException cce) {
                throw new SerializationException("Can't convert key of class " + record.key().getClass().getName() +
                        " to class " + producerConfig.getClass(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG).getName() +
                        " specified in key.serializer", cce);
            }
            byte[] serializedValue;
            try {
                serializedValue = valueSerializer.serialize(record.topic(), record.headers(), record.value());
            } catch (ClassCastException cce) {
                throw new SerializationException("Can't convert value of class " + record.value().getClass().getName() +
                        " to class " + producerConfig.getClass(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG).getName() +
                        " specified in value.serializer", cce);
            }
            int partition = partition(record, serializedKey, serializedValue, cluster);
            tp = new TopicPartition(record.topic(), partition);

通过代码可以知道计算分区号时需要发送记录、序列化后的key、序列化后的value、集群cluster信息。进入patition方法,

/**
     * computes partition for given record.
     * if the record has partition returns the value otherwise
     * calls configured partitioner class to compute the partition.
     */
    private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {
        Integer partition = record.partition();
        return partition != null ?
                partition :
                partitioner.partition(
                        record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
    }

 

通过代码可知:如果我们在发送记录中指定了分区号,那么会直接使用指定的分区,否则:我们去看看Patitioner接口吧。

/**
     * Compute the partition for the given record.
     *
     * @param topic The topic name
     * @param key The key to partition on (or null if no key)
     * @param keyBytes The serialized key to partition on( or null if no key)
     * @param value The value to partition on or null
     * @param valueBytes The serialized value to partition on or null
     * @param cluster The current cluster metadata
     */
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);

通过接口我们可以大致了解到每个参数含义,下面我们来看看实现。

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();
        //如果指定的分区key为空
        if (keyBytes == null) {
            //计算这个主题在这个实例上发送了多少条消息
            int nextValue = nextValue(topic);
            //拿到可用的分区信息
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                // 发送记录总数与可用分区数相除取余(散列)
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                //找到对应的分区
                return availablePartitions.get(part).partition();
            } else {
                //此处英文注释明了
                // no partitions are available, give a non-available partition
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // 存在指定的分区key,则按照murmur2算法路由到对应的分区
            // hash the keyBytes to choose a partition
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }
 private int nextValue(String topic) {
        //每个主题一个发送消息记录的counter,次数通过主题拿到对应的counter(注意是每个实例会有一个counter)
        AtomicInteger counter = topicCounterMap.get(topic);
        if (null == counter) {
            counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
            AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
            if (currentCounter != null) {
                counter = currentCounter;
            }
        }
        //拿到本实例发送的消息记录数
        return counter.getAndIncrement();
    }

综上,我们可以比较清楚的知道我们发送的一个kafka记录是如何路由到对应的分区上了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值