要探索Kafka,首先要了解kafka诞生的背景。Kafka官方下的定义:
Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.
Apache Kafka 作为一款分布式流处理平台,在海量数据处理和实时计算领域发挥着重要作用。其高效的持久化机制是其核心竞争力之一,也是其能够广泛应用于金融、电信、互联网等行业的关键因素。
本文将深入探讨 Kafka 的底层机制,重点分析其高效的持久化机制。
顺序写入 & 稀疏索引
从一开始,是从 Linkedin 内部孵化的项目,Kafka 就是为了解决大数据的实时日志流而生的, 每天要处理的日志量级在千亿规模。
对于日志流的特点主要包括
-
实时产生的数据
-
大规模数据存储与处理
业界针对数据存储(写)的一些解决方案以及思路:
- 内存的存取速度快,但是容量小、价格昂贵,不适用于要长期保存的数据。
- 磁盘的存取速度相对较慢(一次磁盘 IO的耗时主要在寻道时间和盘片旋转时间,但是廉价、而且可以持久化存储。而且磁盘IO性能有提升的方法,比如减少随机 IO,增加顺序 IO
- 磁盘顺序写入速度可以达到几百兆/s,而随机写入速度只有几百KB/s,相差上千倍。此外,磁盘顺序 IO 访问甚至可以超过内存随机 IO 的性能。
总结就是,内存快,但是保存数据不适合,磁盘的 IO 速度慢,但是有正确的使用姿势,其实不一定比内存慢。
针对日志流,想要提高写的效率,让数据以 append 追加的方式顺序写入(write ahead log),使得写入速度非常高(理论上可接近磁盘的写入速度),但是缺乏索引支持,因此查询性能低。
有数据存储,就离不开数据读取(读):
提高读取效率的最常见方案,就是给数据增加索引,通过索引( B+ 树、二份查找树等方式),提高查询速度,但是写入数据时要维护索引,因此会降低写入效率。
常见的几类索引包括:
对于Kafka来说,业务场景的特点是,重写轻读,写的并发非常高,百万级 TPS,但都是顺序写入,无需考虑更新,而读的需求简单,退化到能按照 offset 或者 timestamp 查询消息即可。
如果单纯满足 Kafka 高TPS 的写入操作需求,采用 Append 追加写日志文件的方式显然是最理想的,磁盘顺序写的性能其实也完全够用。
那么如何解决高效查询的问题?
如果采用 B Tree 类的索引结构来实现,每次数据写入时都需要维护索引,而且还会引来"页分裂"等比较耗时的操作。从 Kafka 的实际查询场景来看,B Tree 类的索引并不适用于 Kafka。
那么哈希索引呢,看起来好像非常合适。如果在内存中维护一个从 offset 到日志文件偏移量的映射关系,每次根据 offset 查找消息时,从哈希表中得到偏移量,再去读文件即可。但是哈希索引常驻内存,显然没法处理数据量很大的情况,有可能一个并发,就把内存挤爆了。
其实,消息的 offset 完全可以设计成有序的(实际上是一个单调递增 long 类型的字段),这样消息在日志文件中本身就是有序存放的了,我们便没必要为每个消息建 hash 索引了,完全可以将消息划分成若干个 block,只索引每个 block 第一条消息的 offset 即可,先根据大小关系找到 block,然后在 block 中顺序搜索,这便是 Kafka "稀疏索引" 的来源。
这么来看,为什么Kafka异于MQ ,也能看出来了,通过简化消息模型,将自己退化成了一个海量消息的存储系统。Kafka 在其他功能特性上做了减法,但是在存储上下功夫,做到其他 MQ 无法企及的性能表现。
- 更高的吞吐量: Kafka 顺序写入、稀疏索引等技术可以大幅提升写入效率,使其能够处理百万级别的消息写入。
- 更强的可靠性: Kafka 的副本机制可以保证数据的冗余和可用性,即使部分节点故障,也不会导致数据丢失。
- 更好的扩展性: Kafka 可以通过增加节点数量来实现横向扩展,满足不断增长的数据处理需求
分段存储
了解kafka读写实现,我们回过头了解下Kafka的体系结构:
Kafka是一个分布式流处理平台,它的数据存储和处理机制是基于以下几个核心结构的组合:
主题(Topic):主题是Kafka中数据发布和订阅的逻辑概念,可以看作是数据的类别或者主要标识。生产者将数据发布到特定的主题,而消费者则通过订阅主题来接收相应的数据。
分区(Partition):主题可以被划分为多个分区,每个分区是主题的一个子集,并且在Kafka集群中可以被不同的服务器节点独立存储和处理。分区的作用是实现数据的水平扩展和并行处理。
副本(Replica):为了提高数据的可靠性和容错性,每个分区可以有多个副本。副本是分区的复制,它们分布在不同的服务器节点上,可以提供数据冗余和故障恢复的功能。
分段(Segment):每个分区都会被划分为多个连续的分段,每个分段对应一个物理文件,用于存储分区中的数据。分段的大小可以配置,当分段达到一定大小时,会被关闭并新建一个分段。
索引(Index):每个分段都有一个索引文件,用于快速查找消息的位置信息。索引文件存储了消息的偏移量和物理文件的位置,可以加速消息的读取和查找操作。
Kafka的日志存储结构:
Kafka的分段存储是一种将数据按照分段(Segment)划分并存储的机制。每个分区(Partition)由多个连续的分段组成,每个分段对应一个物理文件,用于存储分区中的数据。
- 分段的划分:
每个分区中的数据被划分为多个连续的分段。当一个分段达到预设的大小限制时,将被关闭并新建一个分段来接收后续的消息。分段的大小可以通过配置进行调整,通常设置为几个千兆字节(GB)。 - 分段文件的命名和存储:
每个分段文件都有一个唯一的文件名,命名规则通常是基于分区和分段的偏移量。Kafka将分段文件存储在磁盘上,可以是本地磁盘或网络存储,例如分布式文件系统。 - 写入和追加操作:
当生产者向Kafka的分区写入消息时,消息首先被追加到当前活跃的分段中。如果当前分段已达到大小限制,将被关闭,并创建一个新的分段来接收后续的消息。这种追加式写入操作可以实现高吞吐量的写入性能。 - 顺序写入和顺序读取:
分段存储使得数据可以以顺序的方式写入和读取。生产者在追加消息时,可以顺序写入当前活跃的分段,而不需要进行随机写入操作。消费者在读取消息时,可以按照顺序从分段文件中读取数据,而不需要进行随机读取操作。这种顺序的写入和读取方式有助于提高磁盘的读写性能。 - 合并和清理:
为了控制分段文件的数量和磁盘空间的使用,Kafka会定期进行合并和清理操作。合并操作将多个较小的分段文件合并为一个更大的分段文件,从而减少文件的数量。清理操作会删除已经被消费者读取过的消息,释放磁盘空间。
分段存储的优势:
- 高吞吐量:追加式写入和顺序读取的方式使得Kafka能够实现高吞吐量的数据处理,适合处理大量的数据流。
- 磁盘空间的有效利用:通过定期的合并和清理操作,Kafka可以控制分段文件的数量,减少磁盘空间的使用。
- 数据的持久化存储:分段存储保证了数据在磁盘上的持久化存储,即使在数据写入后,仍然可供后续的消费者进行读取和处理
副本机制
所谓的副本机制(Replication),也可以称之为备份机制,通常是指分布式系统在多台网络互联的机器上保存有相同的数据拷贝。副本机制有什么好处呢?
1. 提供数据冗余。即使系统部分组件失效,系统依然能够继续运转,因而增加了整体可用性以及数据持久性。
2. 提供高伸缩性。支持横向扩展,能够通过增加机器的方式来提升读性能,进而提高读操作吞吐量。
3. 改善数据局部性。允许将数据放入与用户地理位置相近的地方,从而降低系统延时
我们之前谈到过,Kafka 是有主题概念的,而每个主题又进一步划分成若干个分区。副本的概念实际上是在分区层级下定义的,每个分区配置有若干个副本。
所谓副本(Replica),本质就是一个只能追加写消息的提交日志。根据 Kafka 副本机制的定义,同一个分区下的所有副本保存有相同的消息序列,这些副本分散保存在不同的Broker 上,从而能够对抗部分 Broker 宕机带来的数据不可用。
在实际生产环境中,每台 Broker 都可能保存有各个主题下不同分区的不同副本,因此,单个 Broker 上存有成百上千个副本的现象是非常正常的。
既然分区下能够配置多个副本,而且这些副本的内容还要一致,那么很自然的一个问题就是:我们该如何确保副本中所有的数据都是一致的呢?特别是对 Kafka 而言,当生产者发送消息到某个主题后,消息是如何同步到对应的所有副本中的呢?针对这个问题,最常见的解决方案就是采用基于领导者(Leader-based)的副本机制。
Apache Kafka 就是这样设计的:
-
在kafka中,副本分成两类:领导者副本和追随者副本。每个分区在创建时都要选举一个副本,成为领导者副本,其余的副本自动称为追随者副本。
-
kafka中,追随者副本是不会对外提供服务的,所有的请求都必须由领导者副本来处理。它唯一的任务就是从领导者副本异步拉去消息,并写入到自己提交日志中,从而实现与领导者副本的同步。
-
当领导者副本挂掉了,或者说所在Broker宕机了,kafka可以通过Zookeeper提供的监控功能能够实时感知到,并开启新一轮领导者选举,从追随者副本中选一个作为新的领导者。老Leader副本重启回来后,只能作为追随者副本加入到集群中
汇总下,Kafka 高性能所应用到技术包括:
- 顺序写入: Kafka 将消息追加到日志文件中,顺序写入可以充分利用磁盘的顺序读写性能,大幅提升写入效率。
- 稀疏索引: Kafka 仅为每个消息段的第一条消息创建索引,而非每条消息都创建索引,这种稀疏索引结构可以节省大量的内存空间,同时也能满足快速查找消息的需求。
- 分段存储: Kafka 将每个分区的数据分割成多个段,每个段对应一个文件,这种分段存储方式可以方便地进行数据管理和清理。
- 副本机制: Kafka 为每个分区创建多个副本,并将副本存储在不同的服务器节点上,这种副本机制可以提高数据的可靠性和容错性
参考文档:
- Kafka 官网: kafka.apache.org/
- Kafka 文档: kafka.apache.org/documentati...
- Kafka 源码: github.com/apache/kafk...