Kafka 生产者发送消息的流程

2,338 阅读5分钟

基本概述

  • 一图胜千言,Kafka 生产者发送消息的基本流程,如下图所示:

image.png

  • 总的来说,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 提供了灵活的序列化机制,可以根据需求选择适合的序列化方式。

    1. 键的序列化:键的序列化是可选的,可以通过配置项来设置。如果不需要键,可以将键的序列化器设置为 null。键的序列化器负责将键对象转换为字节数组。常用的键的序列化器包括 StringSerializer IntegerSerializer 等(将 String Integer → byte[])。
    2. 值的序列化:值的序列化是必需的,每条消息都必须有一个值。值的序列化器将值对象转换为字节数组。常用的值的序列化器包括 StringSerializer ByteArraySerializer JsonSerializer 等。可以根据实际情况选择合适的序列化器。(将 byte[] -> String Integer Json
    3. Kafka 生产者使用的序列化器通常需要实现 org.apache.kafka.common.serialization.Serializer 接口,用户可以根据自己的需要定义序列化器。

分区

  • 消息通过send()发送过程中,可能会经过分区器(Partitioner)的作用才能发往 broker 端。如果消息 ProducerRecord 中指定了 partition 字段,那么就不需要分区器的作用,因为 partition 代表的就是所要发往的分区号。
  • Kafka提供了默认分区器org.apache.kafka.clients.producer.internals.DefaultPartitionerpartition()确定分区分配逻辑
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生产者分区写入策略

消息缓冲池

  • 如果每一个消息都单独穿行于网络,会导致大量的网络开销,把消息分成批次传输可以减少网络开销
  • 为了提高效率,消息被分批次写入kafka。一个批次就是一组消息,这些消息(batch)属于同一个主题和分区。
  • 在 Kafka 生产者发送消息的核心逻辑org.apache.kafka.clients.producer.KafkaProducer#doSend()中, ProducerRecord 经过序列化器、分区器处理后,并不是直接发往 broker 端,而是发送到客户端的消息缓冲池(Accumulator) 中,最后交由 Sender 线程发往broker端

Accumulator

在 Kafka 生产者中,Accumulator(累加器)是一个关键组件,用于在发送消息时缓冲和管理待发送的消息记录。Accumulator 的主要作用是将消息记录按照分区进行分组,以便批量发送到 Kafka 集群。

Accumulator 的实现是 RecordAccumulator 类。下面详细介绍 Accumulator 的主要特性和工作原理:

  1. 缓冲区管理:Accumulator 维护一个发送缓冲区,用于存储待发送的消息记录。缓冲区可以容纳多个分区的消息,每个分区有独立的队列。
  2. 分区批处理:Accumulator 将消息按照目标分区进行分组,以便批量发送。每个分区的消息被组织为一个批次(batch),批次中的消息记录将一起发送到对应的分区。
  3. 消息追加:Accumulator 提供 append() 方法,用于将消息记录追加到发送缓冲区中的相应分区。追加操作是非阻塞的,可以立即返回结果。
  4. 批次管理:Accumulator 跟踪每个分区的当前批次,并记录批次的相关信息,如分区、消息的键值、时间戳等。当批次满或者等待时间超过指定阈值时,Accumulator 将触发发送动作。
  5. 批次发送:当满足发送条件时,Accumulator 将批次中的消息记录发送到 Kafka 集群。发送操作是异步的,由 Sender 线程负责实际的网络发送。
  6. 重试机制:如果发送失败或出现错误,Accumulator 负责处理重试逻辑。它可以重新发送失败的消息,或者根据需要创建新的批次进行重试。
  7. 批次大小控制:Accumulator 提供了配置参数,用于控制批次的大小。这样可以平衡网络开销和吞吐量。当批次大小达到阈值时,将触发发送操作。
  8. 阻塞控制: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 的大小可以根据实际的业务情况进行适当调整。