一文了解Kafka的消息收集器RecordAccumulate

〇、前言

在上一篇文章《连Producer端的主线程模块运行原理都不清楚,就敢说自己精通Kafka》中,我们介绍了Main Thread 的工作原理,那么在本篇文章中,我们继续介绍第二部分内容:RecordAccumulator

在介绍原理之前,大家再重温一下Producer端的整体架构,图示如下所示:

这个图看不懂没有关系,我们会在介绍Producer端原理时一一介绍每个部分的含义及其所复杂的功能。

一、RecordAccumulator

在上文中,我们介绍了主线程(Main Thread)的执行流程,当我们使用KafkaProducer发送消息的时候,消息会经过拦截器(Interceptor)、序列化器(Serializer)和分区器(Partitioner),最后会暂存到消息收集器(RecordAccumulator)中,那么,本节就来针对其进行介绍。

RecordAccumulator的主要作用是暂存Main Thread发送过来的消息,然后Sender Thread就可以从RecordAccumulator中批量的获取到消息 ,减少单个消息获取的请求次数,提升性能效率。通过参数buffer.memory可以设置缓存大小(默认32M)。

properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 3210241024);

由于RecordAccumulator的缓存空间有限,如果空间被占满,那么当我们再次调用KafkaProducer的send(...)方法的时候,就会出现阻塞(默认60秒,可以通过参数max.block.ms来配置),如果阻塞超时,则会抛出异常。

properties.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 60*1000);

在RecordAccumulator中,我们通过getOrCreateDeque(...)方法来创建存储消息的数据结构,即:存储ProducerBatch实例对象的双向队列Deque;源码如下所示:

java 复制代码
private final ConcurrentMap<TopicPartition, Deque<ProducerBatch>> batches; // 主题分区:双向队列

private Deque<ProducerBatch> getOrCreateDeque(TopicPartition tp) {
    Deque<ProducerBatch> d = this.batches.get(tp);
    if (d != null)
        return d;
    d = new ArrayDeque<>(); // 创建双向队列
    Deque<ProducerBatch> previous = this.batches.putIfAbsent(tp, d);
    if (previous == null)
        return d;
    else
        return previous;
}

其对应关系是通过一个主题分区对应双向队列Deque<ProducerBatch>,维护在batches中的,如下图所示:

这时可能会有同学问?我记得调用KafkaProducer发送消息的时候,我们发送的是ProducerRecord实例对象,怎么在Deque双向队列中存储的是ProducerBatch实例对象,他们两个有啥区别呢?ProducerRecord是我们使用KafkaProducer发送消息时拼装的单条消息,而ProducerBatch可以看做是针对一批消息进行的封装,因为会在RecordAccumulator中执行tryAppend方法将一批消息拼装在一起,可以减少网络请求次数从而提升吞吐量。

Kafka通过ByteBuffer 来实现字节形式的网络传输,为了减少频繁创建/释放ByteBuffer所造成的资源消耗,Kafka还提供了缓冲池(BufferPool)来实现ByteBuffer的回收,再其内部维护了Deque<ByteBuffer> free变量来保存空闲ByteBuffer,还提供了Deque<Condition> waiters变量来保存阻塞等待中的线程。

如果待分配的size等于缓冲池中ByteBuffer的大小(可由batch.size参数进行配置,默认为16Kb),则直接从free队列中拿出空余的ByteBuffer供其使用;否则,判断如果缓冲池中空闲ByteBuffer的内存总和 加上非缓冲池内存大小 是大于待分配size 的,则采用非缓冲池加上缓冲池混合释放内存的方式进行内存分配。代码如下所示:

关于batch.size参数,除了可以影响BufferPool中缓存的ByteBuffer是否被立刻复用之外,还与创建ProducerBatch有关。当我们通过KafkaProducer发送一条由ProducerRecord封装的消息,并交由RecordAccumulate处理时,会执行如下步骤:

1 】根据主题分区寻找对应的双向队列Deque,从中获取ProducerBatch;

2 】如果这个ProducerBatch还有剩余空间,则直接写入;如果无法写入,则继续执行如下逻辑;

3 】如果待保存的消息size小于等于batch.size,则创建batch.size大小的ProducerBatch ,当使用完毕后,交由BufferPool管理复用;

4 】如果待保存的消息size大于batch.size,那么就创建消息size大小的ProducerBatch ,这段内存区域不会被复用。

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

更多技术干货,欢迎大家关注公众号"爪哇缪斯" ~ \(^o^)/ ~ 「干货分享,每天更新」

相关推荐
爱勇宝3 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒7 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码8 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo9 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志9 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..9 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects10 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰10 小时前
一些基础和问题解决
后端
陈随易10 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN10 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp