打开任何关于 Kafka 的文章,你会看到同样的词用来描述它:
“它是一个开源、分布式、持久、非常可扩展、容错的发布/订阅消息系统,具有丰富的集成和流处理能力。”
虽然这是真的,但对第一次接触 Kafka 的新手读者没有帮助。今天,我们将通过精确分解该定义中每个词的含义来解释 Kafka。每个定义将以💡符号开始。最后,你将对 Apache Kafka 有完整的高级理解。
Kafka 的定义分解
💡 开源(1/8)
Apache Kafka 是数据基础设施空间中最受欢迎的开源项目之一。它是每个数据工程师目录中的标准工具,被超过 70% 的财富 500 强公司和 150,000 个组织使用。
Kafka 是一个消息系统,最初由 LinkedIn 在 2010 年开发。2011 年,它被开源并捐赠给 Apache 基金会。
如今,Confluent 是一家上市公司,被广泛认为是 Kafka 背后的公司。它已扩展业务,在 Kafka 之上提供更完整的数据流平台。
💡 发布/订阅消息系统(2/8)
为什么 Kafka 变得如此广泛使用?
它解决了一个非常重要的问题 - 大规模数据集成问题。LinkedIn 不得不连接不同的服务。实现这一点的天真方法是在每个服务之间创建许多自定义点对点集成(称为数据管道)。这将导致 O(N²) 混乱,当 N 为数百时经常中断且无法维护。

Apache Kafka 将这个问题颠倒过来。它鼓励组织:
- 将数据存储在中央位置(Kafka)
- 使用单一标准 API(Kafka API)
- 让应用订阅并实时消费这些数据
这解耦了写入者和读取者,因为写入者只是发布到 Kafka,读取者订阅 Kafka。
数据被持久化到磁盘有限时间(如 7 天)。Kafka 非常适合并为读扇出用例构建,其中相同消息需要被多个系统读取。因此,系统的读吞吐量是其写吞吐量的倍数是很常见的。

使用 Kafka,组织不需要维护数十个脆弱的自定义点对点管道,这些管道在单个 VM 重启时就会中断。数据可以写入 Kafka 一次,并根据需要被任何系统读取多次。
💡 分布式(3/8)
Apache Kafka 设计为分布式系统 - 一个通过添加更多节点水平扩展的系统。因此,任何正常的 Kafka 部署至少由三个节点组成。
- Broker:Kafka 服务器的实例。这就是我们在系统中所说的节点。
- Cluster:系统中的所有 broker。
💡 可扩展(4/8)
Kafka 具有许多有趣的性能优化(另一篇文章会详细介绍)。它最大的优势是水平可扩展性。
日志数据结构是 Kafka 可扩展性的关键 - 其上的写入是 O(1) 且无锁的。这是因为记录只是追加到末尾,不能更新或单独删除。
分区内的消息彼此独立。它们没有更高级别的保证如唯一键。这减少了对锁定的需求,允许 Kafka 以磁盘允许的速度追加到日志结构。
因为每个分区是一个单独的日志,你可以向集群添加更多 broker,你的规模受限于你可以添加多少 broker。
理论上没有什么阻止你拥有一个接受 50 GiB/s 写入的 Kafka 集群,然后将其扩展 2 倍到 100 GiB/s。

💡 持久(5/8)
Kafka 中的分区不只存在于一个 broker 上 - 它在多个上复制。
一个可配置设置(称为复制因子)表示应该存在多少副本。默认和最常见的是三。
换句话说,我们有日志数据结构的三个副本(称为副本)。这些副本存在于不同 broker 的磁盘上。
复制出于多种原因完成,其中之一是数据持久性:当数据存在三个副本时,一个磁盘故障不会导致数据丢失。
在现代云部署中,broker 分布在不同的可用区。这确保非常高的持久性和可用性。即使整个数据中心烧毁的不太可能事件发生,Kafka 集群仍然会存活。
💡 容错(6/8)
一旦你开始在分布式系统中维护数据副本,你就会打开自己面对许多边缘情况。保持新数据同步是棘手的。副本必须匹配,系统需要以某种方式就最新状态达成一致。
计算机科学中有一整类复杂算法称为分布式共识,处理这个问题。
Kafka 使用直接的单领导者复制模型。在任何时候,一个副本作为领导者。其他两个副本作为追随者(即热备用)。
只有领导者接受新写入 - 它作为日志的真实来源。追随者主动从领导者复制数据。读取可以从领导者和其追随者服务。
当 broker 节点离线时,系统会注意到它。其他 broker 然后接管死 broker 领导的分区领导权。这就是 Kafka 提供高可用性的方式。

核心数据结构
日志
Kafka 构建在简单日志数据结构之上。
它是仅追加的;你只能将记录添加到日志末尾(不允许删除或更新)。读取从左到右,按记录添加的顺序。
日志中的每个记录都有一个唯一的单调递增数字称为偏移量。偏移量引用记录并表示其顺序。

日志数据结构的 API 非常简单:
append(record)- 追加记录到末尾read(offset)- 从偏移量读取记录
Kafka 将日志结构保存在磁盘上。日志的顺序操作与 HDD 非常配合。硬盘为顺序读写提供非常高的吞吐量。这与 HDD 表现不佳的随机读写不同。
消息
- {record, message, event}:日志中的条目。我在描述 Kafka 中的数据时可互换使用这些词。
每个消息本质上是一个键值对;它由一个 byte[] key 和一个 byte[] value 组成(尽管也存在其他元数据如偏移量、时间戳和自定义头)。键是可选的;消息只有值是有效的。

要记住的关键是键/值对是原始字节。
Kafka 本身不支持类型(如 int64、字符串等)或模式(特定消息结构)。
应用客户端代码负责应用模式:
- 写入时:生产者客户端将对象转换(序列化)为字节
- 读取时:消费者客户端将原始字节解析(反序列化)回对象
主题
一个日志不够。你想将数据分成类别。就像在数据库中,你会为用户账户和客户订单创建单独的表;在 Kafka 中,你会创建单独的主题。
Kafka 集群通常有数百到数千个主题是很常见的。
分区
Kafka 是一个分布式系统,设计为扩展到远超单台机器能处理的范围。因此,它使用分片。
主题被分片为一个或多个分区。
每个分区是日志数据结构的单独实例。
虽然主题可以只有一个分区,但通常有数十个分区是很常见的,因为这有助于读取并行化(稍后详细介绍)。

客户端 API
Kafka 不使用 HTTP。它使用自己基于 TCP 的协议。这意味着你需要更多自定义代码来发送和接收请求;你不能只使用任何 HTTP 库。
Kafka 提供自己的实现底层协议的库。你关心的主要客户端是生产者和消费者。
- Producer:用于向 Kafka 写入数据的类
- Consumer:用于从 Kafka 读取数据的类
Apache Kafka 项目提供实现这些的 Java 库:
import org.apache.kafka.clients.producer.KafkaProducer;import org.apache.kafka.clients.consumer.KafkaConsumer;Producer 类允许你向主题发送消息。你可以显式选择分区或让 Kafka 自动为你完成。
Consumer 类允许你订阅并读取来自主题(或特定分区)的消息流。
消费者组
回想一下日志是按顺序读取的:
- 主题被分成分区,因为它是大数据™ - 单个节点不应该能够消费整个主题。你需要许多消费者来处理高容量主题数据。
- 一次只有一个消费者应该从一个分区读取。这是为了确保消息顺序而无需锁。
- 这些消费者需要协调如何在彼此之间分配分区。
- 同时,Kafka 的目标是允许并行消费(多个读取者)同一分区以实现高读扇出情况。
Kafka 通过消费者组解决这个问题。这些组是一组消费者客户端实例(通常在单独节点上),作为一个连贯单元操作。它们在彼此之间分配工作。
每个消费者组独立地以自己的速度读取主题。同一组内的消费者在彼此之间分配分区。
消费者组支持动态成员资格 - 你可以通过在运行时添加或移除成员来扩展消费上下。

本质上,Kafka 中的读吞吐量可以通过两种不同方式扩展:
- 向组添加更多消费者:例如,你的主题从 10 MB/s 增加到 20 MB/s 吞吐量,你现有的两个消费者跟不上。添加更多以承担额外负载。
- 添加更多消费者组:例如,你的主题正在被消费,但你想让新的、单独的应用类型也处理数据。例如,夜间会计作业想赶上最后一天的支付数据。
同一组内的 Kafka 消费者不彼此交谈。他们通过 Kafka broker 间接协调。领导特定组的 broker 称为组协调器。
Kafka 再次使用集中协调模型 - 组协调器做出决策。消费者向协调器心跳,并通过基于拉的模式告知自己应该做什么工作。
组协调器还充当”数据库”,存储每个消费者的进度。
消费者组在称为 __consumer_offsets 的特殊 Kafka 主题中将 {partition, offset} 的简单映射存储为组。这帮助他们保存读取进度的进度。
当消费者读取消息时,它通过协调器 broker 提交已处理日志的偏移量。这种定期检查点允许平滑故障转移。在故障事件中,消费者可以重启并从结束的地方恢复,或者另一个消费者可以来接替工作。
特殊偏移主题有许多分区分布在 broker 之间。每个组与特定分区关联。该分区的领导者充当该组的组协调器。从这个意义上说,集群中的每个 broker 都可以充当某些消费者组的协调器。这防止了一个 broker 处理集群中所有消费者组的热点。
本文为学习目的的个人翻译,译文仅供参考。
原文链接:How Kafka Works。
版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。