本篇论文发表于2011年,应该是 Kafka 在 Linkedin 内部应用不久便发布的(可以从论文中的描述得知,可能更接近0.7版本),所以对于 Kafka 的架构设计及部分功能的与后续被广泛使用时会有显著差距,但核心的内容并无二致。
Kafka 诞生的初衷是用于处理海量的日志数据,所以相较传统的消息(Pub/Sub)系统拥有更高的吞吐量。而在 Kafka 之前对于日志的处理大多采用主动抓取的方式采集到离线系统中进行分析。而在 Linkedin 内容有一些对日志分析的相对较实时性的需求。所以催生出了 Kafka 的开发。
在阅读论文及本文之前,关于 Kafka 有比较多个概念,为便于接下来阅读,可以先了解基本的 Kafka 相关概念进行预习或复习。
论文原文:Kafka: a Distributed Messaging System for Log Processing
概述
在本篇论文中整个 Kafka 的架构还比较简单,对于 Broker 组成的集群并没有主节点的含义,所有 Broker 实例的职责一致而且功能并不是十分复杂。如下图所示,一个 Topic 可以在不同 Broker 上的多个 Partition,一个 Producer 对应一个 Partition,一个 Partition 可以对应多个 Consumer 。
简单的存储结构
Kafka 的存储结构布局比较简单,主题(Topic)的每个分区(Partition)对应一个逻辑日志(logic log),日志采用长度相同(约1G)的段文件组成。段文件内容刷新到磁盘基于一定时机,并非有新数据便写入磁盘。对于Producer 发送的消息,只有包含该消息的日志被刷新到磁盘,该条消息才会提供给 Consumer。
通常的消息系统会通过一个连续自增的 ID 标识一条消息,并通过该 ID 关联消息的实际位置与长度信息等。不同于通常的消息系统, Kafka 是在通过逻辑偏移量对存储在日志中的消息进行寻址。当然 Kafka 的信息也有 ID ,只不过这个 ID 并非连续自增的,ID 中会记录消息的长度,上面的逻辑偏移量便可通过这个长度进行计算。
如下图所示,Broker 会在内存中维护一个偏移量索引,记录了每个段文件的起始消息条目,其中每个条目中 msg- 前缀后面的内容便是消息体长度,即逻辑偏移量。像是下图中 segment file 1 这个段文件中最后一个条目的消息开始位置为 14516809 ,而未在图中显示的 segment file 2 这个段文件中第一个条目的消息开始位置为 14517018 。而在 14516809 - 14517017 之间的内容均为 msg-00014516809 编号对应的消息体。
Kafka 采用上述方式存储消息的好处是避免了运行过程中,大量的对文件随机检索操作,基于偏移量的处理,可以基于文件的顺序读写来提升消息落盘相关操作。
高效的数据传递
通过这篇论文,对于数据的高效传输部分主要集中在以下 Page Chache 和 Zero Copy 两个部分,再不考虑论文中未提及的设计与实现。
Page Cache
首先 Kafka 对消息的缓存只依赖操作系统的 Page Cache 而不会在进程中缓存消息(对于运行在 VM 上的语言来讲是 GC 友好的)。基于操作系统的 Page Cache 的 write-through(提示:数据会先写入cache中待时机合适时写入磁盘) 以及 read-ahead(提示:操作系统通过预先读取连续的多个page放入cache中提升性能) 特性,可以进一步增强数据读写性能。
针对 write-through、*read-ahead 特性,因为 Kafka 的应用特点,一条刚刚由 Producer 发送到 Broker 落盘的消息,会随之被 Consumer 消费读取,而且基于上面提到的偏移量的消息定位机制,所以 Page Cache 这两个特性对性能的提升有一定辅助作用。
Zero Copy
另一处优化点是沦为了 Kafka 的一个高频面试题,也就是通常所说的 “零拷贝”(Zero Copy)。其实没有上一步中提到的,Kafka 没有采用进程缓存消息的设计为前提的话,Zero Copy 的优化可能也无从谈起了。
Kafka 通过 Linux/Unix 系统提供的 sendfile
API 在数据网络传输过程中避免了 2 次数据拷贝和 1 次系统调用。具体可以参考下图,下图基于论文描述内容所绘,通过图示其实相当于减少了1次系统调用和1次数据拷贝。
简洁无状态Broker
Kafka 的 Broker 相对简单而且还是无状态设计,由各 Consumer 自身维护其消费了多少消息,而 Broker 不用关心是否每个 Consumer 均消费了某条消息。对于消息数据的删除清理机制也相对简单,Broker 基于指定过期时间,当消息在 Broker 中保留了一定天数便会进行删除处理。这样还可以使得 Consumer 支持将消息的偏移量倒回以便对某些消息进行重新消费(也是基于 Kafka 的消息消费是基于拉模型,如果是推模型可能并不容易实现,也会使 Broker 变得复杂)。
分布式协调机制
在这篇论文中提及的对 ZooKeeper 的使用还比较简单,而且与现在的 Kafka 实现上差异较大。
Kafka 利用 ZooKeeper 主要实现下面三个功能,可以结合下面的配图进行理解:
如下图所示,其中绿色方框的 Broker/Consumer/Ownership Register 都是 ZooKeeper 中的临时节点,而黑色方框的 Offset Register 则是持久节点。
Consumer的再平衡机制
这里针对论文中提供的算法描述,以发起再平衡的 Consumer 为主视角概括为如下流程:
这里补充一个不足以整除的例子
1 个 Topic 对应 3 Partition,只有 2 个 Consumer 的情况。分配后的关系如下:Consumer - 1 : Topic:A Partition -1 ; Topic: A Partition - 2
Consumer - 2 : Topic:A Partition - 3
上述的算法还比较简单,对应了使用 Kafka 时可选择的 Range 算法策略,当然后续 基于已排序的 Consumer 和 Partition , 提供了更均衡的策略。
消息投递保障
Kafka 保证消息的投递是至少 1 次,所以需要应用端针对重复数据做处理。Kafka 保证来自单个分区的消息按顺序传递给 Consumer。但是不能保证来自不同分区的消息的顺序。
未来将开展的工作