千亿消息“过眼云烟”?Kafka把硬盘当内存用的性能魔法,全靠这一手!

Kafka 消息队列

Apache Kafka是一个开源的分布式消息队列,由LinkedIn公司开发并于2011年贡献给Apache软件基金会。Kafka被设计用来处理千亿量级的实时数据,被广泛应用于互联网大规模数据处理平台中。

Kafka强大的数据吞吐量,其中最重要部分在于它的消息日志格式的设计,包括几个特别重要的概念,主题(topic)、分区(partition)、分段(segment)、偏移量(offset)。

1)主题:Kafka按主题对消息进行分类。 主题是逻辑上的概念。

2)分区:硬盘实际根据分区存储日志。一个主题下通常有多个分区,分区分布在不同的broker上,这使Kafka提供给了并行的消息处理和横向扩容能力。并且分区通常会分组,每组有一个主分区、多个副本分区,并分布在不同的broker上,从而起到容灾的作用。

3)分段:宏观上看,一个分区对应一个日志(Log)。由于生产者生产的消息会不断追加到log文件末尾,为了防止 log 日志过大,Kafka 又引入了日志分段(LogSegment)的概念,将 log 切分为多个 LogSegement,相当于一个巨型文件被平均分配为相对较小的文件,这样也便于消息的维护和清理。

Log 和 LogSegement 也不是纯粹物理意义上的概念,Log 在物理上只是以文件夹的形式存储,而每个 LogSegement 对应于磁盘上的一个日志文件(.log)和两个索引文件(.index、timeindex),以及可能的其他文件(比如以.txindex为后缀的事务索引文件)。

4)偏移量:消息在日志中的位置,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量。偏移量是消息在分区中的唯一标识,是一个单调递增且不变的值。Kafka 通过它来保证消息在分区内的顺序性,不过偏移量并不跨越分区,也就是说,Kafka 保证的是分区有序而不是主题有序。

日志格式

Kafka日志是以批为单位进行日志存储的,所谓的批指的是Kafka会将多条日志压缩到同一个批次(Batch)中,然后以Batch为单位进行后续的诸如索引的创建和消息的查询等工作。对于每个批次而言,其默认大小为4KB,并且保存了整个批次的起始偏移量和时间戳等元数据信息,而对于每条消息而言,其偏移量和时间戳等元数据存储的则是相对于整个批次的元数据的增量,通过这种方式,Kafka能够减少每条消息中数据占用的磁盘空间。

Kafka将日志消息只写入Page Cache,而Page Cache中的数据通过Linux的flusher程序进行异步刷盘,将数据顺序追加写到硬盘日志文件中。由于Page Cache是在内存中进行缓存,因此读写速度非常快。顺序追加写充分利用顺序 I/O 写操作,可有效提升Kafka 的吞吐量。Kafka甚至鼓励使用足够的内存让活跃数据集能完全驻留在Page Cache中。

具体到日志文件中的每个 LogSegement 都有一个基准偏移量 (baseOffset),用来标识当前 LogSegement 中第一条消息的 offset。偏移量是一个 8字节的长整形。日志文件和两个索引文件都是根据基准偏移量命名的,名称固定为 20 位数字,没有达到的位数则用 0 填充。比如第一个 LogSegment 的基准偏移量为 0,对应的日志文件为 00000000000000000000.log。

示例中第 2 个 LogSegment 对应的基准偏移量是 256,也说明了该 LogSegment 中的第一条消息的偏移量为 256,同时可以反映出第一个 LogSegment 中共有 256 条消息(偏移量从 0 至 255 的消息)。

日志索引

Kafka主要有两种类型的索引文件:偏移量索引文件(.indx)和时间戳索引文件(.timeindex)。偏移量索引文件中存储的是消息的偏移量与该偏移量所对应的消息的物理地址;时间戳索引文件中则存储的是消息的时间戳与该消息的偏移量值。

Kafka中的索引文件以稀疏索引(Sparse index)的方式构造消息的索引,它并不保证每个消息在索引文件中都有对应的索引项。如上日志格式中所述,Kafka 至少写入 4KB 消息数据之后,才会在索引文件中增加一个索引项。

偏移量索引

偏移量索引分为2个部分,总共占8个字节。

1)relativeOffset(4B):消息的相对偏移量,即offset - baseOffset,其中baseOffset为整个segmentLogFile的起始消息的offset。

2)position(4B):物理地址,也就是日志在分段日志文件中的实际位置。

假设 Kafka 需要找出偏移量为 23 的消息:

1)首先通过二分法找到不大于23的最大偏移量索引【22,656】;

2)然后从position(656)开始顺序查找偏移量为23的消息。

时间戳索引

时间戳索引分为2部分,公占12个字节。

1)timestamp:当前日志分段文件中建立索引的消息的时间戳。

2)relativeoffset:时间戳对应消息的相对偏移量。

假设 Kafka 需要找出偏移量为 28 的消息:

1)在时间戳索引文件中找到不大于该时间戳的最大时间戳对应的最大索引项【1526384718283,28】;

2)在偏移量索引文件中检索不超过对应relativeoffset(28)的最大偏移量索引的项【26,838】;

3)按照偏移量索引的检索方式找到对应的具体消息。

日志清理

Kafka提供了两种日志清理策略。

1)日志删除(Log Deletion):按照一定的保留策略来直接删除不符合条件的日志分段。

2)日志合并(Log Compaction):针对每个消息的key进行整合,对于有相同key的的不同value值,只保留最后一个版本。

日志删除

Kafka的默认清理策略是日志删除,主要有三种方式。

1)基于日志大小:检查当前日志的大小size是否超过设定的阈值retentionSize的差值,来寻找可删除的日志分段的文件集合deletableSegments。

2)基于时间:检查当前日志文件中是否有保留时间超过设定的阈值retentionMs,来寻找可删除的的日志分段文件集合deletableSegments。

3)基于日志起始偏移量:检查某日志分段的下一个日志分段的baseOffset是否小于等于logStartOffset,来寻找可删除的的日志分段文件集合deletableSegments。

日志合并

对于特定主题(通常用于存储状态变更或配置信息),可以开启日志合并。它确保对于每个消息Key,分区中至少保留其最新Value的记录。旧版本的值会被清理。压缩过程在后台进行,扫描日志段,对具有相同Key的消息,只保留最新偏移量(即最新版本)的那条。已标记为删除(Value为null)的Key,若其所有版本都被压缩,则该Key最终会被移除。

这类似于LSM树的合并思想,适用于那些只需要关心每个Key最新状态的场景。

总结: 殊途同归,拥抱顺序I/O的制胜法宝

在现代高性能存储系统的设计中,一个关键出发点在于将存储介质的物理特性(如速度、成本、寿命和访问模式)与实际业务需求(如读写比例、数据量和延迟要求)完美结合,并通过合适的数据结构和I/O策略实现最优匹配。

以 MySQL的 InnoDB引擎为例,为应对关系型数据库中复杂的范围查询和排序需求,InnoDB 采用了B+树索引。这种结构具备高扇出、多层级的特点,能够将逻辑查找高效映射到磁盘页访问。同时,B+树的叶子节点之间通过链表连接,配合顺序预读和操作系统的Page Cache机制,使得在读密集型或混合负载场景下具备良好性能。

RocksDB则采用了不同的路径。它基于 LSM 树的设计理念,先将写入数据暂存于内存(MemTable),再批量、有序地写入磁盘(SSTable 文件)。后台的合并机制异步合并文件、清理冗余,从而保持读写平衡。这种将随机写转化为顺序写的方式,特别适合SSD介质,能大幅提升写入吞吐量。

在 Kafka 中,核心思路则是将消息写入分区日志,以追加的方式顺序写入磁盘。每个分区天然支持并行扩展,Kafka 利用了机械硬盘的顺序写优势,同时在 SSD 上也有良好的性能表现。加上操作系统 Page Cache 的高效缓存机制,它在实际场景中甚至可以提供接近内存级别的读写速度。

这三种系统虽然应用场景不同,底层结构各异,但有一个共识:顺序 I/O 是提升存储系统性能的关键路径。它们都充分利用了 Page Cache 和底层硬件的特点,体现了技术设计中对硬件现实的尊重与对系统性能的极致追求。

很高兴与你相遇!

如果你喜欢本文内容,记得关注哦!!!

相关推荐
纪莫2 小时前
Kafka如何保证「消息不丢失」,「顺序传输」,「不重复消费」,以及为什么会发生重平衡(reblanace)
java·分布式·后端·中间件·kafka·队列
武子康5 小时前
大数据-75 Kafka 高水位线 HW 与日志末端 LEO 全面解析:副本同步与消费一致性核心
大数据·后端·kafka
齐木卡卡西在敲代码1 天前
kafka的pull的依据
分布式·kafka
超级迅猛龙1 天前
保姆级Debezium抽取SQL Server同步kafka
数据库·hadoop·mysql·sqlserver·kafka·linq·cdc
ejinxian1 天前
MySQL/Kafka数据集成同步,增量同步及全量同步
数据库·mysql·kafka
poemyang1 天前
Facebook内部都在用的存储引擎,LSM凭什么能硬扛亿级写入流量?
存储·pagecache·lsm tree·顺序i/o·局部性原理
武子康1 天前
大数据-74 Kafka 核心机制揭秘:副本同步、控制器选举与可靠性保障
大数据·后端·kafka
程序员不迷路1 天前
Kafka学习
分布式·kafka
bing_1581 天前
kafka 生产者是如何发送消息的?
分布式·kafka