要深入学习Kafka,理解Kafka的存储机制是非常重要的。本文介绍Kafka存储消息的格式以及数据文件和索引组织方式,以便更好的理解Kafka是如何工作的。

Kafka消息存储格式

Kafka为了保证消息的可靠性,服务端会将接收的消息进行序列化并保存到磁盘上(Kafka的多副本存储机制),这里涉及到消息的存储格式,即消息编码后落到磁盘文件上的二进制的数据格式。下图是根据Kafka 3.0官方文档整理的消息格式:

包含三个部分:BatchRecords、Record,以及Header的编码格式。

BatchRecords是Kafka数据的存储单元,一个BatchRecords中包含多个Record(即我们通常说的一条消息)。BatchRecords中各个字段的含义如下:

字段名 含义
baseOffset 这批消息的起始Offset
partitionLeaderEpoch 用于Partition的Recover时保护数据的一致性,具体场景可以见KIP101
batchLength BatchRecords的长度
magic 魔数字段,可以用于拓展存储一些信息,当前3.0版本的magic是2
crc crc校验码,包含从attributes开始到BatchRecords结束的数据的校验码
attributes int16,其中bit0~2中包含了使用的压缩算法,bit3是timestampType,bit4表示是否失误,bit5表示是否是控制指令,bit6~15暂未使用
lastOffsetDelta BatchRecords中最后一个Offset,是相对baseOffset的值
firstTimestamp BatchRecords中最小的timestamp
maxTimestamp BatchRecords中最大的timestamp
producerId 发送端的唯一ID,用于做消息的幂等处理
producerEpoch 发送端的Epoch,用于做消息的幂等处理
baseSequence BatchRecords的序列号,用于做消息的幂等处理
records 具体的消息内容

一个BatchRecords中可以包含多条消息,即上图中的Record,而每条消息又可以包含多个Header信息,Header是Key-Value形式的。Record和Header的格式都非常简单,就不对其中的字段做解释了。

Log Segment

在Kafka中,一个Topic会被分割成多个Partition,而Partition由多个更小的,称作Segment的元素组成。

Kafka一个Partition下会包含类似上图中的一些文件,由log、index、timeindex三个文件组成一个Segment,而文件名中的(0和35)表示的是一个Segment的起始Offset(Kafka会根据log.segment.bytes的配置来决定单个Segment文件(log)的大小,当写入数据达到这个大小时就会创建新的Segment)。log、index、timeindex中存储的都是二进制的数据(log中存储的就是上一部分介绍的BatchRecords的内容,而index和timeindex分别是一些索引信息。)

下图是log文件中数据解析后的示意图(也就是本文第一部分BatchRecords格式)。其中16开头的这一行表示一个第一条消息的Offset是16的BatchRecord,而24开头的这一行表示的是一个第一条消息的Offset是24的BatchRecord。

索引

我们知道Kafka中每个Consumer消费一个Partition都会有一个关联的Offset表示已经处理过的消息的位置。通常Consumer会根据Offset连续的处理消息。而通过Offset从存储层中获取消息大致分为两步:

  • 第一步,根据Offset找到所属的Segment文件

  • 第二步,从Segment中获取对应Offset的消息数据

其中第一步可以直接根据Segment的文件名进行查找(上面已经介绍了Segment的文件面就是它包含的数据的起始Offset)。第二步则需要一些索引信息来快速定位目标数据在Segment中的位置,否则就要读取整个Segment文件了,这里需要的索引信息就是上面的index文件存储的内容。

index文件中存储的是Offset和Position(Offset对应的消息在log文件中的偏移量)的对应关系,这样当有Offset时可以快速定位到Position读取BatchRecord,然后再从BatchRecord中获取某一条消息。比如上述Offset25会被定位到24这个BatchRecord,然后再从这个BatchRecord中取出第二个Record(24这个BatchRecord包含了24、25两个Record)。

注意,Kafka并不会为每个Record都保存一个索引,而是根据log.index.interval.bytes等配置构建稀疏的索引信息。

除了index索引文件保存Offset和Position的映射关系外,Kafka中还维护了timeindex,保存了Timestamp和Offset的关系,用于应对一些场景需要根据timestamp来定位消息。timeindex中的一个(timestampX,offsetY)元素的含义是所有创建时间大于timestampX的消息的Offset都大于offsetY。

同样的,timeindex也采用了稀疏索引的机制,使用和index相同的配置(log.index.interval.bytes),所以timeindex和index是一一对应的。

总结

本文首先介绍了Kafka消息的存储格式,然后介绍了Kafka是如何索引(index & timeindex)存储的数据的。看完索引部分后遗留了一个疑问:每次读取消息都要先根据索引读取Position信息,然后再根据Position去读数据,而索引又是稀疏索引(查找索引也是要开销的),这样效率是否会比较低呢?如果利用Consumer总是顺序读取消息的特性,每次读取数据时都带上一些上下文信息(比如上一次Offset对应的Position信息)直接去读Log数据效率是否会更高?欢迎留言交流!

Kafka消息(存储)格式及索引组织方式的更多相关文章

  1. Kafka消息系统基础知识索引

    一些观念的修正 从 0.9 版本开始,Kafka 的标语已经从“一个高吞吐量,分布式的消息系统”改为"一个分布式流平台". Kafka不仅仅是一个队列,而且是一个存储,有超强的堆积 ...

  2. Kafka(3)--kafka消息的存储及Partition副本原理

    消息的存储原理: 消息的文件存储机制: 前面我们知道了一个 topic 的多个 partition 在物理磁盘上的保存路径,那么我们再来分析日志的存储方式.通过 [root@localhost ~]# ...

  3. Kafka消息文件存储

    在对消息进行存储和缓存时,Kafka依赖于文件系统.(Page Cache) 线性读取和写入是所有使用模式中最具可预计性的一种方式,因而操作系统采用预读(read-ahead)和后写(write-be ...

  4. 一文看懂Kafka消息格式的演变

    摘要 对于一个成熟的消息中间件而言,消息格式不仅关系到功能维度的扩展,还牵涉到性能维度的优化.随着Kafka的迅猛发展,其消息格式也在不断的升级改进,从0.8.x版本开始到现在的1.1.x版本,Kaf ...

  5. 转载来自朱小厮博客的 一文看懂Kafka消息格式的演变

    转载来自朱小厮博客的 一文看懂Kafka消息格式的演变     ✎摘要 对于一个成熟的消息中间件而言,消息格式不仅关系到功能维度的扩展,还牵涉到性能维度的优化.随着Kafka的迅猛发展,其消息格式也在 ...

  6. Kafka分片存储、消息分发和持久化机制

    Kafka 分片存储机制 Broker:消息中间件处理结点,一个 Kafka 节点就是一个 broker,多个 broker 可以组成一个 Kafka集群. Topic:一类消息,例如 page vi ...

  7. MyISAM的前缀压缩索引在索引块中的组织方式

    纯粹自己的理解,哪位大佬看到了还请指正. 首先贴一张<高性能MySQL>中的一段话: 这句话的意思是说,MyISAM使用b+树组织索引.也就是说无论索引压缩与否,组织方式一定是B+树. 下 ...

  8. 联合索引在B+树上的存储结构及数据查找方式

    能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 引言 上一篇文章<MySQL索引那些事>主要讲了MySQL索引的底层原理,且对比了B ...

  9. Kafka日志存储原理

    引言 Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相互独立的.每个topic又可以分成几个不同的partition(每个topic有几个partition是在创建 ...

随机推荐

  1. Java动态代理底层实现

    Java实现源码 上一节我们提到了Java动态代理的使用,接下来我们看一下他的具体实现. HelloInterface proxyHello = (HelloInterface) Proxy.newP ...

  2. PyQT5:信号和槽

    PyQT5:信号和槽 信号和槽 Qt的主要特征之一是它使用信号和插槽在对象之间进行通信. 当潜在的事件发生时,会发出一个信号.插槽是可调用的Python,如果将信号连接到插槽,则在发出信号时将调用该插 ...

  3. LCT 小记

    全程 Link-Cut Tree,是解决动态树问题的有力科技 --题记 简单实现 LCT 的形态直观上是一堆 Splay 的合体,每个 Splay 以时间戳为关键字,各个 Splay 通过虚边相连,可 ...

  4. Apache Hudi内核之文件标记机制深入解析

    1. 摘要 Hudi 支持在写入时自动清理未成功提交的数据.Apache Hudi 在写入时引入标记机制来有效跟踪写入存储的数据文件. 在本博客中,我们将深入探讨现有直接标记文件机制的设计,并解释了其 ...

  5. HCNP Routing&Switching之IS-IS报文结构和类型

    前文我们了解了IS-IS动态路由协议基础相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15249328.html:今天我们来聊一聊IS-IS动态路由协 ...

  6. SpringBoot系列——附件管理:整合业务表单实现上传、回显、下载

    前言 日常开发中,大多数项目都会涉及到附件上传.回显.下载等功能,本文记录封装通用附件管理模块,并与业务模块进行整合实现上传.回显.下载 我们之前已经对文件上传下载有过记录,传送门:基于"f ...

  7. ansible 批量安装yum包

    1.首先安装一下ansible yum install ansible 2.修改一下ansible的参数以防ssh过去的时候需要首次判断yes  或者no sed -i 's/#host_key_ch ...

  8. 聊聊ReentrantLock基于AQS的公平锁和非公平锁的实现区别

    ReentrantLock锁的实现是基于AQS实现的,所以先简单说下AQS: AQS是AbstractQueuedSynchronizer缩写,顾名思义:抽象的队列同步器,它是JUC里面许多同步工具类 ...

  9. mybatis零碎

    <           <    小于号 >          >    大于号 &     &        和 &apos;     '     单 ...

  10. git合并内容的1个方式

    诉求 把自己的改动的文件合并到目标分支: 不想改那些不属于自己开发时出现的冲突: 操作 开发分支dev,要合并至master分支 1 所有改动都已提交,保持分支的干净状态(working tree c ...