kafka04- 生产者RecordAccumulator工作原理深入探讨

目录

kafka生产者发送消息的主要流程

RecordAccumulator的关键特性和工作原理

关键特性

工作原理

RecordAccumulator内存申请流程

消息大小<=16KB

申请内存

释放内存

 消息大小>16KB

申请内存

释放内存

池化内存和非池化内存的转化

1 池化内存转换成非池化内存

2 非池化内存转换成池化内存

3 内存不足发生阻塞的场景

4 是否需要修改batch.size的默认值


kafka生产者发送消息的主要流程

  1. 创建ProducerRecord

    • 应用程序首先将待发送的数据封装成一个ProducerRecord对象。这个对象包含了消息的Topic、可选的Key和Value,以及时间戳等信息。
  2. 序列化

    • Kafka是面向字节的系统,因此ProducerRecord中的Key和Value需要序列化成字节序列。这通常通过序列化器(Serializer)来完成,例如StringSerializerByteArraySerializer
  3. 选择分区

    • Kafka使用分区机制来保证消息的顺序性和扩展性。Producer根据Key(如果提供)和分区器(Partitioner)来决定将消息发送到哪个分区。如果没有Key,则使用轮询或其他自定义分区算法。
  4. 获取元数据

    • Producer需要知道每个TopicPartition的Leader是谁,这样才能知道消息应该发送到哪个Broker。这通过获取集群的元数据来实现,元数据也包含了分区信息和副本信息。
  5. 缓存到RecordAccumulator

    • 序列化后的消息被缓存到RecordAccumulator中。这是一个内存缓冲区,用于收集将要发送的消息批次。它允许Producer收集多个消息,然后批量发送,以提高效率。
  6. 等待触发条件

    • 消息在RecordAccumulator中等待,直到满足以下条件之一:
      • 达到batch.size指定的批次最大大小。
      • 达到linger.ms指定的延迟时间,即使批次尚未满。
  7. 分批发送

    • 当触发条件满足时,消息批次将被封闭并准备发送。Producer会根据消息的目标分区,将批次分发到不同的分区队列中。
  8. Sender子线程处理

    • Kafka Producer内部有一个或多个Sender线程(取决于max.in.flight.requests.per.connection的配置)。这些线程负责实际的网络发送工作。
  9. 使用NIO发送

    • Sender线程使用Java NIO(非阻塞I/O)机制,将消息批次异步发送到对应的Broker。NIO允许在单个线程内处理多个网络连接,从而提高网络操作的效率。
  10. 回调处理

    • 每条消息发送后,如果提供了回调函数,Sender线程会在消息发送完成后调用它,以便应用程序可以处理发送结果,例如记录日志或更新状态。
  11. 确认和重试

    • Producer根据acks配置等待Broker的确认。如果发送失败,根据retriesretry.backoff.ms配置,Producer可能会重试发送消息。
  12. 错误处理

    • 如果消息最终无法被发送(例如,因为Broker不可用或网络问题),Producer将调用每条消息的回调函数,并传递一个异常。

RecordAccumulator的关键特性和工作原理

关键特性
  1. 内存池管理RecordAccumulator使用内存池来管理内存,这有助于减少因频繁分配和释放内存导致的性能问题。

  2. 按TopicPartition缓存:消息根据TopicPartition进行缓存,相同TopicPartition的消息被放在同一个Deque<ProducerBatch>队列中。

  3. 批量处理:通过将消息批量处理,RecordAccumulator减少了网络请求的次数,提高了发送效率。

  4. 动态内存分配RecordAccumulator可以动态地分配和释放内存,以适应不同大小的消息和不同的发送需求。

  5. 配置驱动RecordAccumulator的行为可以通过参数如batch.sizebuffer.memory进行配置。

工作原理
  1. 消息序列化与分区:消息首先被序列化,然后根据分区算法确定目标TopicPartition

  2. 缓存分配RecordAccumulator根据batch.sizebuffer.memory参数分配内存。如果请求的内存大小等于ProducerBatch的大小(例如16KB),它会尝试从内存池中复用已有的ByteBuffer

  3. 内存池复用

    • 如果可用内存块的大小与请求的大小相匹配,并且内存池中有可用的内存块,就直接复用。
    • 如果内存块大小匹配,但在内存池中没有可用的内存块,就需要从JVM堆内存中分配新的内存。
  4. 内存释放

    • ProducerBatch被发送后,内存可以被释放回RecordAccumulator
    • 如果释放的内存块大小等于poolableSize,它会被添加到空闲列表中,以便再次使用。
    • 如果大小不匹配,内存会被添加到nonPooledAvailableMemory中,等待JVM的垃圾回收。
  5. 内存分配策略

    • 对于大小等于poolableSize的内存块,RecordAccumulator会尝试重用这些内存块,以减少垃圾回收的需要。
    • 对于其他大小的内存块,它们将被返回给JVM,依赖垃圾回收机制来释放。
  6. 等待和唤醒机制:如果内存不足,发送消息的线程可能会被阻塞,直到有足够的内存可用。一旦内存被释放,等待的线程将被唤醒。

  7. 内存管理优化:通过这种方式,RecordAccumulator减少了对JVM垃圾回收的依赖,从而提高了性能。

通过这种设计,RecordAccumulator不仅提高了消息发送的吞吐量和效率,还通过减少垃圾回收的需求来优化内存使用,这对于高性能的Kafka Producer至关重要。

RecordAccumulator内存申请流程

如果此时生产者生成了一条消息,并且计算出了应该将消息发送到哪个 TopicPartition。接下来我们看看 RecordAccumulator.append() 方法的执行流程

消息大小<=16KB

在消息大小小于或等于16KB的场景中,Kafka Producer使用RecordAccumulator进行内存管理和消息发送的流程如下:

申请内存
  1. 预估消息大小:首先,Producer预估消息M的大小,包括序列化后的Key、Value和Headers的大小。

  2. 确定批量大小:如果预估的消息大小小于或等于batch.size(默认为16KB),则使用batch.size作为批量大小创建ProducerBatch

  3. 从内存池获取ByteBuffer

    • 如果内存池(Deque<ByteBuffer> free)中有等于batch.size大小的空闲ByteBuffer,则直接从队首获取一个ByteBuffer对象使用。
    • 这是通过if (size == poolableSize && !this.free.isEmpty()) return this.free.pollFirst();实现的。
  4. 创建ProducerBatch:使用获取到的ByteBuffer创建一个ProducerBatch,并将消息追加到这个批次中。

  5. 等待批量发送条件:当ProducerBatch被填满(达到batch.size)或等待时间达到linger.ms设置的值时,Sender线程会将这个批次发送到Broker。

释放内存
  1. 发送后释放:Sender线程在将ProducerBatch成功发送到Broker后,会释放该批次占用的内存。

  2. 清空ByteBuffer:通过调用ByteBuffer.clear()清空ByteBuffer中的数据,准备重复使用。

  3. 归还到内存池

    • 如果释放的ByteBuffer大小等于poolableSize(默认16KB),则将其归还到内存池的队尾,以便后续的ProducerBatch使用。
    • 这是通过buffer.clear(); this.free.add(buffer);实现的。
  4. 唤醒等待线程:如果有其他Producer线程因为内存不足而处于等待状态,此时会唤醒它们,以便它们可以继续执行消息发送操作。

  5. 依赖JVM GC:如果释放的ByteBuffer大小不等于poolableSize,则将其归还到nonPooledAvailableMemory中,等待JVM的垃圾回收器进行回收。

 消息大小>16KB

在消息大小大于16KB的发送场景中,Kafka Producer的RecordAccumulator如何申请和释放内存的流程如下:

申请内存
  1. 预估消息大小:首先,Producer预估消息M的大小,包括序列化后的Key、Value和Headers的大小。

  2. 确定批量大小:如果预估的消息大小大于batch.size(默认为16KB),则使用消息预估的大小作为批量大小创建ProducerBatch

  3. 从非池化内存申请内存

    • 由于消息大小超过了默认的池化大小,不能从Deque<ByteBuffer> free中申请内存,而需要从非池化内存nonPooledAvailableMemory中申请。
    • 首先计算当前可用的总内存,包括nonPooledAvailableMemory和内存池中可回收的内存大小(freeListSize)。
  4. 检查可用内存

    • 如果当前空闲的总内存大于或等于需要申请的内存大小,则可以继续申请内存。
  5. 调整可用内存值

    • nonPooledAvailableMemory中减去申请的内存大小,更新可用内存的值。
  6. 分配内存

    • 执行allocate方法从JVM堆内存中分配所需大小的内存给ProducerBatch
  7. 发送消息

    • 将大于16KB的消息放入新创建的ProducerBatch中,并等待发送条件满足后,由Sender线程发送到Broker端。
释放内存
  1. 发送后释放:Sender线程在将ProducerBatch成功发送到Broker后,会释放该批次占用的内存。

  2. 归还内存到非池化内存

    • 由于释放的ByteBuffer大小不等于poolableSize(默认16KB),不能直接放入内存池复用,而是将其大小加回到nonPooledAvailableMemory中。
  3. 依赖JVM GC

    • 释放的内存需要依赖JVM的垃圾回收器进行回收,以便内存最终被释放。
  4. 唤醒等待线程

    • 如果有其他Producer线程因为内存不足而处于等待状态,此时会唤醒它们,以便它们可以继续执行消息发送操作。
  5. 内存管理

    • 通过deallocate方法管理内存的归还和线程的唤醒,确保内存得到合理利用和回收。

池化内存和非池化内存的转化

1 池化内存转换成非池化内存
  1. 内存申请需求:当需要发送大于默认batch.size(如16KB)的消息时,比如32KB,Producer需要从非池化内存nonPooledAvailableMemory中申请内存。

  2. 检查非池化内存:如果nonPooledAvailableMemory中的可用内存不足以满足申请需求,Producer会尝试从池化内存Deque<ByteBuffer> free中释放一些ByteBuffer来补充nonPooledAvailableMemory

  3. 释放池化内存:Producer会从free队列中逐个释放ByteBuffer(每个默认16KB),直到nonPooledAvailableMemory中的内存足够申请新的ProducerBatch

  4. 内存转换:此过程涉及到将池化内存中的ByteBuffer释放并转移到非池化内存中,以满足大消息的发送需求。

  5. 内存归还:发送完成后,归还的内存需要通过JVM GC来释放。

2 非池化内存转换成池化内存
  1. 内存申请:当发送的消息较小,需要的ProducerBatch小于或等于默认batch.size时,如果池化内存free中没有足够的ByteBuffer,Producer会从nonPooledAvailableMemory中划分一部分内存到池化内存。

  2. 内存划分:将非池化内存中的内存划分出来,并将其放入free队列的头部。

  3. 创建ProducerBatch:使用新划分的内存创建ProducerBatch,用于存放小消息。

  4. 内存释放与复用:消息发送后,释放的内存直接放入free队尾,通过ByteBuffer.clear()清空数据,实现内存的复用,不触发JVM GC。

3 内存不足发生阻塞的场景
  1. 内存申请阻塞:如果内存不足,申请内存的线程可能会阻塞,直到有足够的内存可用。

  2. 唤醒等待线程:一旦内存被释放并满足申请需求,等待的线程将被唤醒。

4 是否需要修改batch.size的默认值
  1. 参数调优batch.size的值可以根据实际场景进行调整,以优化吞吐量和内存利用率。

  2. 性能测试:调整batch.size需要基于充分的性能测试,以确定最优值。

  3. 内存利用率:增大batch.size可能会降低内存利用率,特别是当大容量的ProducerBatch只包含小消息时。

  4. 参数权衡:需要在内存利用率和吞吐量之间找到平衡点,合理设置batch.size

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值