Kafka源码剖析 —— 生产者消息追加中的优秀设计与大体流程

本文深入剖析Kafka生产者发送消息的流程,包括拦截器、消息分区、RecordAccumulator的分段锁与双重检查锁、RecordBatch和MemoryRecord的使用。详细讲解了内存管理,特别是ByteBuffer的扩容与内存池机制,强调了合理配置避免大消息对效率的影响。
摘要由CSDN通过智能技术生成

消息发送流程:

这里写图片描述

消息发送组件:

其中消息的追加包含以下几个组件。我们在KafkaProducer中调用send方法发送一个消息,在消息追加步骤,最终是将消息添加到了ByteBuffer中。

这里写图片描述

消息追加器流程:

这里写图片描述

一、KafkaProducer

1.1、拦截器的实现

我们发现在send的时候,如果存在拦截器,则调用onSend方法。

    @Override
    public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
        // intercept the record, which can be potentially modified; this method does not throw exceptions
        ProducerRecord<K, V> interceptedRecord = this.interceptors == null ? record : this.interceptors.onSend(record);
        return doSend(interceptedRecord, callback);
    }

onSend方法的实现非常简单,实际上这就是将注册到该Producer的拦截器进行轮询,并进行调用,从源码中我们也可以知道,这个拦截器是有顺序要求的,解析配置文件时是依半角逗号来隔开的。

    public ProducerRecord<K, V> onSend(ProducerRecord<K, V> record) {
        ProducerRecord<K, V> interceptRecord = record;
        for (ProducerInterceptor<K, V> interceptor : this.interceptors) {
            try {
                interceptRecord = interceptor.onSend(interceptRecord);
            } catch (Exception e) {
                // do not propagate interceptor exception, log and continue calling other interceptors
                // be careful not to throw exception from here
                if (record != null)
                    log.warn("Error executing interceptor onSend callback for topic: {}, partition: {}", record.topic(), record.partition(), e);
                else
                    log.warn("Error executing interceptor onSend callback", e);
            }
        }
        return interceptRecord;
    }

// 解析配置时:

    case LIST:
        if (value instanceof List)
            return (List<?>) value;
        else if (value instanceof String)
            if (trimmed.isEmpty())
                return Collections.emptyList();
            else
                return Arrays.asList(trimmed.split("\\s*,\\s*", -1));
        else
            throw new ConfigException(name, value, "Expected a comma separated list.");

所以我们的配置文件可以这么写,来注册拦截器,注意,拦截器必须继承ProducerInterceptor。我们在InterceptorPlus 中,把我们即将发送的value强转为了int,然后为其++;

 Properties props = new Properties();
 String classNames = InterceptorPlus.class.getName() + "," + InterceptorMultiply.class.getName();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.CLIENT_ID_CONFIG, "ProducerTest");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.IntegerSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, classNames);

// 拦截器:

/**
 * Created by Anur IjuoKaruKas on 2018/9/5
 */
public class InterceptorPlus implements ProducerInterceptor {
   
    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        Integer val = Integer.valueOf(record.value()
                                            .toString());
        String result = String.valueOf(val + 1);
        return new ProducerRecord(record.topic(),
            record.key(),
            result);
    }
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
    }
    @Override
    public void close() {
    }
    @Override
    public void configure(Map<String, ?> configs) {
    }
}
1.2、消息分区的实现

首先,先从元数据拿到集群的信息,集群信息中的partitions是以 Map<\String, List<\PartitionInfo>>来存储的。这里根据我们指定的topic名字来获取partitions。这里通过两种方式来确定消息发往哪个分区:

  • 如果未指定key:在这个分区器中,保存了一个全局的AtomicInteger counter,通过一个简单的取模来确定到底发送到哪一个分区,DefaultPartitioner.toPositive(int num)方法来确保一个值一定为正数,我们知道负数取模是会得到一个负数的。
  • 如果指定了key:我们知道,消息可以被指定一个key。DefaultPartitioner.toPositive(Utils.murmur2(keyBytes)) % numPartitions,同一个key,hash算出来的数字总会是相同的,只要保证分区数量不变,就可以计算出同一个partition,而同一个partition,可以确保消息发送的顺序。
   /**
     * Compute the partitio
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值