温馨提示:本文基于 Kafka 2.2.1 版本。本文主要是以源码的手段一步一步探究消息发送流程,如果对源码不感兴趣,可以直接跳到文末查看消息发送流程图与消息发送本地缓存存储结构图。
从上文 初识 Kafka Producer 生产者,可以通过 KafkaProducer 的 send 方法发送消息,send 方法的声明如下:
Future<RecordMetadata> send(ProducerRecord<K, V> record)
Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback)
从上面的 API 可以得知,用户在使用 KafkaProducer 发送消息时,首先需要将待发送的消息封装成 ProducerRecord,返回的是一个 Future 对象,典型的 Future 设计模式。在发送时也可以指定一个 Callable 接口用来执行消息发送的回调。
我们在学习消息发送流程之前先来看一下用于封装一条消息的 ProducerRecord 的类图,先来认识一下 kafka 是如何对一条消息进行抽象的。
1、ProducerRecord 类图
我们首先来看一下 ProducerRecord 的核心属性,即构成 消息的6大核心要素:
-
String topic
消息所属的主题。 -
Integer partition
消息所在主题的队列数,可以人为指定,如果指定了 key 的话,会使用 key 的 hashCode 与队列总数进行取模来选择分区,如果前面两者都未指定,则会轮询主题下的所有分区。 -
Headers headers
该消息的额外属性对,与消息体分开存储. -
K key
消息键,如果指定该值,则会使用该值的 hashcode 与 队列数进行取模来选择分区。 -
V value
消息体。 -
Long timestamp 消息时间戳,根据 topic 的配置信息 message.timestamp.type 的值来赋予不同的值。
-
CreateTime
发送客户端发送消息时的时间戳。 -
LogAppendTime
消息在 broker 追加时的时间戳。
-
其中Headers是一系列的 key-value 键值对。
在了解 ProducerRecord 后我们开始来探讨 Kafka 的消息发送流程。
2、Kafka 消息追加流程
KafkaProducer 的 send 方法,并不会直接向 broker 发送消息,kafka 将消息发送异步化,即分解成两个步骤,send 方法的职责是将消息追加到内存中(分区的缓存队列中),然后会由专门的 Send 线程异步将缓存中的消息批量发送到 Kafka Broker 中。
消息追加入口为 KafkaProducer#send
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.onSend(record); // @1
return doSend(interceptedRecord, callback); // @2
}
代码@1:首先执行消息发送拦截器,拦截器通过 interceptor.classes 指定,类型为 List< String >,每一个元素为拦截器的全类路径限定名。
代码@2:执行 doSend 方法,后续我们需要留意一下 Callback 的调用时机。
接下来我们来看 doSend 方法。
2.1 doSend
KafkaProducer#doSend
ClusterAndWaitTime clusterAndWaitTime;
try {
clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
} catch (KafkaException e) {
if (metadata.isClosed())
throw new KafkaException("Producer closed while send in progress", e);
throw e;
}
long remainingWaitMs = Math.max(0, ma