阅读:Kafka a Distributed Messaging System for Log Processing

2023年 10月 16日 26.2k 0

本篇论文发表于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 paper old architecture.png

简单的存储结构

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 segmant in-memory relation.png
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次数据拷贝。

Kafka paper zero-copy.png

简洁无状态Broker

Kafka 的 Broker 相对简单而且还是无状态设计,由各 Consumer 自身维护其消费了多少消息,而 Broker 不用关心是否每个 Consumer 均消费了某条消息。对于消息数据的删除清理机制也相对简单,Broker 基于指定过期时间,当消息在 Broker 中保留了一定天数便会进行删除处理。这样还可以使得 Consumer 支持将消息的偏移量倒回以便对某些消息进行重新消费(也是基于 Kafka 的消息消费是基于拉模型,如果是推模型可能并不容易实现,也会使 Broker 变得复杂)。

分布式协调机制

在这篇论文中提及的对 ZooKeeper 的使用还比较简单,而且与现在的 Kafka 实现上差异较大。

Kafka 利用 ZooKeeper 主要实现下面三个功能,可以结合下面的配图进行理解:

  • 检测 Broker 和 Consumer 的增加和删除:具体来说就是当每个 Broker/Consumer 启动时,它将其信息存储在 Zookeeper 中的 Broker/Consumer 注册表(可以简单理解为一个指定的父节点,该节点下的子节点存储着 Broker 和 Consumer 信息)中。Broker 注册表包含 Broker 的主机名和端口,以及存储在其上的一组主题和分区等信息。Consumer 注册表包括 其所属的 Consumer Group 及其订阅的主题集等信息。
  • Consumer 监听 Broker/Consumer 的变化执行再平衡:Consumer 启动后会针对 Broker/Consumer 注册表建立 Watcher 机制,当 Broker 或 Consumer 的数量发生变更时,当前 Consumer 会执行再平衡机制,重新为当前 Consumer 的每个 Topic 匹配 Partition。
  • 维护消费关系与跟踪每个分区已消费偏移量:Consumer 完成再平衡机制后,会将自身与 Partition 的关系写入 Ownership 注册表,每次拉取消息也会将偏移量信息实时更新到 Offset 注册表中。
  • 如下图所示,其中绿色方框的 Broker/Consumer/Ownership Register 都是 ZooKeeper 中的临时节点,而黑色方框的 Offset Register 则是持久节点。

    Kafka paper ZooKeeper struct.png

    Consumer的再平衡机制

    这里针对论文中提供的算法描述,以发起再平衡的 Consumer 为主视角概括为如下流程:

  • 从 Ownership Register 中移除当前 Consumer 与 Broker 中的 Partition 的关系数据,并读取 Broker 与 Consumer Register 数据,计算出各 Topic 下可用的 Broker 下的 Partition 数量(为了便于描述这里计为P)和 Consumer 数量(为了便于描述这里计为C),并将 Consumer 和 Partition 按照固定规则进行排序;
  • 通过 P 、 C 计算出各个 Topic 下每个 Consumer 可以分配多少 Partition (为了便于描述这里将该数量用N表示);N=PCN = frac PCN=CP​
  • 基于已排序的 Consumer 和 Partition ,获取 Consumer 在 Consumer Group 中的索引位置 j ,以及分配数量 N ,可获取最终分配给该 Consumer 的 Partition 索引起至值(如下公式中的 start 与 end);start=j∗N;end=(j+1)∗(N−1) start=j*N; end=(j+1) * (N-1)start=j∗N;end=(j+1)∗(N−1)
  • 这里补充一个不足以整除的例子
    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。但是不能保证来自不同分区的消息的顺序。

    未来将开展的工作

  • 多个 Broker 之间的消息冗余复制:用于增强可用性。并同时支持异步及同步复制模型,以便在生产者延迟和保证提供的强度之间进行一些权衡(据了解在 0.8 版本之后引入了分区的副本机制)。
  • 增加 “流” 处理能力:主要也是基于 Linkedin 自身的应用场景考虑,可以简化需要实时处理的应用程序对 Kafka 的使用(据了解在 0.10 版本之后实现了 Kafka Streams);
  • 相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论