欢迎光临
我们一直在努力

【Kafka源码解析和使用指南】第17篇:MemoryRecords与RecordBatch源码解析——消息是怎么被“打包“的

上一篇【第16篇】RecordAccumulator源码深度解析——Kafka生产者的\”消息缓冲区\”秘密 下一篇【第18篇】BufferPool源码解析——Kafka生产者的\”内存管家\”


摘要

消息到了RecordAccumulator,最终被\”打包\”放在哪里?答案是MemoryRecords——一个封装了Java NIO ByteBuffer的数据容器,再加上Compressor提供的压缩能力,组成了Kafka最底层的消息存储单元。RecordBatch则是在MemoryRecords之上加了一层管理外壳,负责追踪批次状态、管理回调队列。本文将从MemoryRecords的ByteBuffer封装说起,解析RecordBatch的完整结构,梳理消息格式从V0到V2的演进历程,最后对比GZIP/Snappy/LZ4/ZSTD四种压缩算法的特性。读完这篇,你会真正理解Kafka消息\”长什么样子\”。


一、MemoryRecords——消息的\”物理容器\”

1.1 整体架构

【MemoryRecords 核心结构】

MemoryRecords
┌─────────────────────────────────────────────┐
│ │
│ buffer: ByteBuffer ←── 消息数据的存储容器 │
│ writeLimit: int ←── buffer最多写入多少 │
│ writable: boolean ←── 是否可写模式 │
│ │
│ compressor: Compressor (可选,压缩器) │
│ ┌────────────────────────────────────┐ │
│ │ appendStream: DataOutputStream │ │
│ │ │ (装饰器: 提供压缩功能) │ │
│ │ ▼ │ │
│ │ bufferStream: ByteBufferOutputStream│ │
│ │ │ (装饰器: 提供自动扩容功能) │ │
│ │ ▼ │ │
│ │ buffer: ByteBuffer ←── 最终目的地 │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

1.2 压缩器的装饰器模式

这是Kafka中装饰器模式(Decorator Pattern)的经典应用:

// Compressor构造时根据压缩类型选择不同的包装层
public Compressor(ByteBuffer buffer, CompressionType type) {


this.type = type;
// 第一层:ByteBuffer → ByteBufferOutputStream(自动扩容)
bufferStream = new ByteBufferOutputStream(buffer);
// 第二层:根据压缩类型选择合适的输出流装饰器
appendStream = wrapForOutput(bufferStream, type, COMPRESSION_DEFAULT_BUFFER_SIZE);
}

public static DataOutputStream wrapForOutput(
ByteBufferOutputStream buffer,
CompressionType type, int bufferSize) {


try {


switch (type) {


case NONE:
// 不压缩:直接包装DataOutputStream
return new DataOutputStream(buffer);
case GZIP:
// GZIP压缩:JDK内置,直接new
return new DataOutputStream(
new GZIPOutputStream(buffer, bufferSize));
case SNAPPY:
// Snappy压缩:非JDK标准库,反射创建(避免依赖缺失报错)
OutputStream stream = (OutputStream)
snappyOutputStreamSupplier.get()
.newInstance(buffer, bufferSize);
return new DataOutputStream(stream);
case LZ4:
// LZ4压缩:同样使用反射方式创建
// …类似SNAPPY的实现
default:
throw new IllegalArgumentException(
\”Unknown compression type: \” + type);
}
} catch (IOException e) {


throw new KafkaException(e);
}
}

为什么GZIP直接new,Snappy/LZ4却用反射?答:GZIP的GZIPOutputStream是JDK内置的,但Snappy和LZ4需要额外引入JAR包。用反射可以做到——不引入Snappy包时,代码照样编译通过,只有当实际使用时才加载类。这是\”可选的依赖\”的经典处理方式。

1.3 MemoryRecords的核心方法

public class MemoryRecords {

// 构造方法是私有的,只能通过emptyRecords()创建
private MemoryRecords(ByteBuffer buffer, CompressionType compressionType,

赞(0)
未经允许不得转载:171主机测评 » 【Kafka源码解析和使用指南】第17篇:MemoryRecords与RecordBatch源码解析——消息是怎么被“打包“的
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址