基本概述
- 一图胜千言,Kafka 生产者发送消息的基本流程,如下图所示:
- 总的来说,Kafka 的 producer 发送消息采用的是异步发送的方式。在消息发送过程中,涉及到了两个线程—— main 线程和 sender 线程,以及一个线程共享变量——RecordAccumulator。
- main 线程将消息发送给 RecordAccumulator,sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka broker。
发送消息的流程
构建消息记录(ProducerRecord)
主线程负责创建要发送的消息记录。这包括指定主题、分区、键、值以及其他相关的信息。
//消息记录 ProducerRecord 的属性如下
public class ProducerRecord<K, V> {
private final String topic; //目标主题
private final Integer partition; //分区
private final Headers headers; //头信息
private final K key; //消息key
private final V value; //消息体
private final Long timestamp; //消息时间戳
}
//构建 ProducerRecord
@Override
public ListenableFuture<SendResult<K, V>> send(String topic, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, data);
return doSend(producerRecord);
}
序列化
-
在 Kafka 生产者中,主线程将消息包装成消息记录(ProducerRecord)之后,由于消息需要在网络中进行传输,不可避免的需要进行序列化操作。
-
Kafka 提供了灵活的序列化机制,可以根据需求选择适合的序列化方式。
- 键的序列化:键的序列化是可选的,可以通过配置项来设置。如果不需要键,可以将键的序列化器设置为 null。键的序列化器负责将键对象转换为字节数组。常用的键的序列化器包括
StringSerializer IntegerSerializer等(将String Integer → byte[])。 - 值的序列化:值的序列化是必需的,每条消息都必须有一个值。值的序列化器将值对象转换为字节数组。常用的值的序列化器包括
StringSerializer ByteArraySerializer JsonSerializer等。可以根据实际情况选择合适的序列化器。(将byte[] -> String Integer Json) - Kafka 生产者使用的序列化器通常需要实现
org.apache.kafka.common.serialization.Serializer接口,用户可以根据自己的需要定义序列化器。
- 键的序列化:键的序列化是可选的,可以通过配置项来设置。如果不需要键,可以将键的序列化器设置为 null。键的序列化器负责将键对象转换为字节数组。常用的键的序列化器包括
分区
- 消息通过
send()发送过程中,可能会经过分区器(Partitioner)的作用才能发往 broker 端。如果消息 ProducerRecord 中指定了 partition 字段,那么就不需要分区器的作用,因为 partition 代表的就是所要发往的分区号。 - Kafka提供了默认分区器
org.apache.kafka.clients.producer.internals.DefaultPartitioner,partition()确定分区分配逻辑
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster,
int numPartitions) {
if (keyBytes == null) {
return stickyPartitionCache.partition(topic, cluster);
}
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
分区策略
消息缓冲池
- 如果每一个消息都单独穿行于网络,会导致大量的网络开销,把消息分成批次传输可以减少网络开销
- 为了提高效率,消息被分批次写入kafka。一个批次就是一组消息,这些消息(batch)属于同一个主题和分区。
- 在 Kafka 生产者发送消息的核心逻辑
org.apache.kafka.clients.producer.KafkaProducer#doSend()中, ProducerRecord 经过序列化器、分区器处理后,并不是直接发往 broker 端,而是发送到客户端的消息缓冲池(Accumulator) 中,最后交由 Sender 线程发往broker端
Accumulator
在 Kafka 生产者中,Accumulator(累加器)是一个关键组件,用于在发送消息时缓冲和管理待发送的消息记录。Accumulator 的主要作用是将消息记录按照分区进行分组,以便批量发送到 Kafka 集群。
Accumulator 的实现是 RecordAccumulator 类。下面详细介绍 Accumulator 的主要特性和工作原理:
- 缓冲区管理:Accumulator 维护一个发送缓冲区,用于存储待发送的消息记录。缓冲区可以容纳多个分区的消息,每个分区有独立的队列。
- 分区批处理:Accumulator 将消息按照目标分区进行分组,以便批量发送。每个分区的消息被组织为一个批次(batch),批次中的消息记录将一起发送到对应的分区。
- 消息追加:Accumulator 提供 append() 方法,用于将消息记录追加到发送缓冲区中的相应分区。追加操作是非阻塞的,可以立即返回结果。
- 批次管理:Accumulator 跟踪每个分区的当前批次,并记录批次的相关信息,如分区、消息的键值、时间戳等。当批次满或者等待时间超过指定阈值时,Accumulator 将触发发送动作。
- 批次发送:当满足发送条件时,Accumulator 将批次中的消息记录发送到 Kafka 集群。发送操作是异步的,由 Sender 线程负责实际的网络发送。
- 重试机制:如果发送失败或出现错误,Accumulator 负责处理重试逻辑。它可以重新发送失败的消息,或者根据需要创建新的批次进行重试。
- 批次大小控制:Accumulator 提供了配置参数,用于控制批次的大小。这样可以平衡网络开销和吞吐量。当批次大小达到阈值时,将触发发送操作。
- 阻塞控制:Accumulator 通过等待时间控制阻塞的行为。当发送缓冲区已满或者等待时间超过指定阈值时,Accumulator 将阻塞等待,直到有足够的空间或者时间到达。
批量发送
-
发送到缓冲池中消息将会被分为一个一个的 batch ,分批次的发送到 broker 端,批次大小由参数batch.size 控制,默认16KB。这就意味着正常情况下消息会攒够16KB时才会批量发送到broker端,所以一般减小batch大小有利于降低消息延时,增加batch大小有利于提升吞吐量。
batch.size:只有数据积累到batch.size后,sender才会发送数据。(单位:字节,注意:不是消息个数)。
-
但是消息并不是必须要达到一个batch尺寸才会批量发送到服务端呢,Producer端提供了另一个重要参数
linger.ms,用来控制batch最大的空闲时间,超过该时间的batch也会被发送到broker端。linger.ms如果数据迟迟未达到 batch.size,sender等待linger.ms之后也会发送数据。(单位:毫秒)。
其他
-
对应上文中的 -- 8.阻塞控制
buffer.memory: 缓冲池最大大小由参数buffer.memory控制,默认是32M。max.block.ms:当生产消息的速度过快导致 buffer 满了的时候,将阻塞max.block.ms时间,超时抛异常,所以 buffer 的大小可以根据实际的业务情况进行适当调整。