Kafka生产者发送消息源码分析

1,433 阅读3分钟

久违了,各位,好久没写文章了,今天给大家来一篇Kafka生产者发送消息的源码分析吧!

最近笔者项目中用了kafka,也分析了一下kafka发送消息的源码,就把整个发送流程梳理了一下,跟大伙分析一下。

通常在工作中我们都是这样发送消息的,send方法就是本次源码分析的入口:

调用send方法发送后用Future接收结果,很显然,消息发送的过程是个异步的过程(不熟悉的朋友去可以去看下JDK的Future接口实现)

image.png

进入到一个重载方法,这里并没有指定callback,如果实现了callback,发送消息后将会异步回调callback实现

image.png

interceptors很显然是一个拦截器链,在消息发送之前可以通过拦截器去做一些事情,这是经典的责任链的设计模式

image.png

进入doSend()方法(方法比较长,直接把源码复制过来了)


private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) {
    TopicPartition tp = null;
    try {
        // first make sure the metadata for the topic is available
        ClusterAndWaitTime clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
        long remainingWaitMs = Math.max(0, maxBlockTimeMs - clusterAndWaitTime.waitedOnMetadataMs);
        Cluster cluster = clusterAndWaitTime.cluster;
        byte[] serializedKey;
        try {
            serializedKey = keySerializer.serialize(record.topic(), record.headers(), record.key());//序列化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());//序列化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);

        setReadOnly(record.headers());
        Header[] headers = record.headers().toArray();

        int serializedSize = AbstractRecords.estimateSizeInBytesUpperBound(apiVersions.maxUsableProduceMagic(),
                compressionType, serializedKey, serializedValue, headers);
        ensureValidRecordSize(serializedSize);
        long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp();
        log.trace("Sending record {} with callback {} to topic {} partition {}", record, callback, record.topic(), partition);
        // producer callback will make sure to call both 'callback' and interceptor callback
    // 看下拦截器链存不存在,有的话,连同callback实现一起进行回调。
        Callback interceptCallback = this.interceptors == null ? callback : new InterceptorCallback<>(callback, this.interceptors, tp);

        if (transactionManager != null && transactionManager.isTransactional())
            transactionManager.maybeAddPartitionToTransaction(tp);

    //将数据放到客户端的一个队列缓冲区内暂存
        RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey,
                serializedValue, headers, interceptCallback, remainingWaitMs);
    //一旦队列满了,唤醒消息sender线程,将数据进行批量发送,发送是通过一种NIO的方式进行发送的
        if (result.batchIsFull || result.newBatchCreated) {
            log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition);
            this.sender.wakeup();
        }
        return result.future;
        // handling exceptions and record the errors;
        // for API exceptions return them in the future,
        // for other exceptions throw directly
    } catch (ApiException e) {
        log.debug("Exception occurred during message send:", e);
        if (callback != null)
            callback.onCompletion(null, e);
        this.errors.record();
        if (this.interceptors != null)
            this.interceptors.onSendError(record, tp, e);
        return new FutureFailure(e);
    } catch (InterruptedException e) {
        this.errors.record();
        if (this.interceptors != null)
            this.interceptors.onSendError(record, tp, e);
        throw new InterruptException(e);
    } catch (BufferExhaustedException e) {
        this.errors.record();
        this.metrics.sensor("buffer-exhausted-records").record();
        if (this.interceptors != null)
            this.interceptors.onSendError(record, tp, e);
        throw e;
    } catch (KafkaException e) {
        this.errors.record();
        if (this.interceptors != null)
            this.interceptors.onSendError(record, tp, e);
        throw e;
    } catch (Exception e) {
        // we notify interceptor about all exceptions, since onSend is called before anything else in this method
        if (this.interceptors != null)
            this.interceptors.onSendError(record, tp, e);
        throw e;
    }
}

int partition = partition(record, serializedKey, serializedValue, cluster);

这里通过特定的规则获取分区号,下一步从这里进去看看如何确定消息发往哪个分区的

如果发送的消息指定了分区,就发送到用户指定的分区

image.png

如果没有指定分区,先看keyBytes参数是否为空,如果不为空,通过keyBytes%总分区数确定分区号, 如果keyBytes为空,从kafka集群中获取可用分区,如果可用分区数>0,通过下面方式去确定一个position,通过position%可用分区数确定分区号,否则通过position%总分区数确定分区号

image.png

image.png

现在让我们回到sender.wakeup();这里,很明显,这是一个发送消息的IO线程,这块的源码比较复杂,涉及到非常多NIO的东西,但是核心做的事情就是将缓冲区队列中的数据批量发送到kafka的broker中。

消息发送完后,就会通过Future接收消息发送结果

Future<RecordMetadata> future = kafkaProducer.send(record);

这里能够接收到记录的元数据,比如消息投递到了哪个topic,哪个分区,offset是什么等等信息。

整体发送的流程图就是这样,图片转载自www.cnblogs.com/wsw-seu/p/1…

image.png

最后

最近这段时间笔者都在学算法,作为算法菜鸟级选手,之前落下的基础很多,现在都在一点点补,有时候觉得算法也蛮有意思的,算法和数据结构学习过程仿佛就是一个从正常人的思维逻辑降维到计算机的角度去思考逻辑的过程。我们平时玩的一些小游戏,其实里面都会涉及各种大大小小的算法,在玩游戏的时候,是否思考过消消乐的消除逻辑是怎么实现。在调用一些排序API的时候,是否思考过里面用到了10种经典排序算法的哪一种或者哪几种。

如果有读者和我一样在学算法,可以加我一起交流,一起刷题。