Apache Kafka 基准测试:每秒 200 万次写入(在三台廉价机器上)
翻译:sealos 是以kubernetes为内核的云操作系统发行版,3min 一键高可用安装自定义kubernetes,500M,100年证书,版本不要太全,生产环境稳如老狗。
我写了一篇关于 LinkedIn 如何使用Apache Kafka作为中央发布-订阅日志的博客文章,用于在应用程序、流处理和 Hadoop 数据摄取之间集成数据。
但是,要真正完成这项工作,这个“通用日志”必须是一种廉价的抽象。如果您想将系统用作中央数据中心,它必须快速、可预测且易于扩展,以便您可以将所有数据转储到该系统上。我的经验是,脆弱或昂贵的系统不可避免地会形成一道保护过程墙,以防止人们使用它们;易于扩展的系统通常最终成为关键的架构构建块,因为使用它是构建事物的最简单方法。
我一直很喜欢 Cassandra 的基准测试,它显示它在EC2和Google Compute Engine上的三百台机器上每秒执行一百万次写入。我不知道为什么,也许这是邪恶博士的事情,但每秒做一百万件事情很有趣。
无论如何,Kafka 日志的优点之一是,正如我们将看到的,它很便宜。每秒一百万次写入并不是什么特别大的事情。这是因为日志比数据库或键值存储要简单得多。事实上,我们的生产集群全天每秒进行数千万次读取和写入,而且它们是在相当普通的硬件上完成的。
但是让我们做一些基准测试并看看。
卡夫卡30秒
为了帮助理解基准,让我快速回顾一下 Kafka 是什么以及它如何工作的一些细节。Kafka 是一个分布式消息系统,最初是在 LinkedIn 构建的,现在是Apache 软件基金会的一部分,并被各种公司使用。
一般设置非常简单。生产者将记录发送到保存这些记录并将它们分发给消费者的集群:
Kafka 的关键抽象是主题。生产者将他们的记录发布到一个主题,消费者订阅一个或多个主题。Kafka 主题只是一个分片的预写日志。生产者将记录附加到这些日志中,消费者订阅更改。每条记录都是一个键/值对。密钥用于将记录分配给日志分区(除非发布者直接指定分区)。
这是一个简单的例子,一个生产者和消费者从两个分区的主题中读写。
这张图片显示了一个附加到两个分区的日志的生产者进程,以及一个从相同日志中读取的消费者。日志中的每条记录都有一个关联的条目号,我们称之为偏移量。消费者使用这个偏移量来描述它在每个日志中的位置。
这些分区分布在一组机器上,允许一个主题保存比任何一台机器都多的数据。
请注意,与大多数消息传递系统不同,日志始终是持久的。收到消息后会立即将其写入文件系统。消息在阅读时不会被删除,但会保留一些可配置的 SLA(比如几天或一周)。这允许在数据使用者可能需要重新加载数据的情况下使用。它还可以支持节省空间的发布-订阅,因为无论有多少消费者,都有一个共享日志;在传统的消息传递系统中,每个消费者通常都有一个队列,因此添加消费者会使数据大小翻倍。这使得 Kafka 非常适合常规消息传递系统范围之外的事物,例如充当 Hadoop 等离线数据系统的管道。这些离线系统可能仅作为周期性 ETL 周期的一部分每隔一段时间加载,
Kafka 还将其日志复制到多个服务器上以实现容错。与其他消息传递系统相比,我们的复制实现的一个重要架构方面是复制不是需要复杂配置的外来附加组件,仅用于非常特殊的情况。相反,复制被假定为默认值:我们将未复制的数据视为复制因子恰好为 1 的特殊情况。
生产者在发布包含记录偏移量的消息时会收到确认。发布到分区的第一条记录的偏移量为 0,第二条记录的偏移量为 1,依此类推,以不断增加的顺序。消费者从偏移指定的位置消费数据,并通过定期提交将其位置保存在日志中:保存此偏移以防消费者实例崩溃并且另一个实例需要从其位置恢复。
好的,希望这一切都有意义(如果没有,您可以在此处阅读更完整的 Kafka 介绍)。
本基准
这个测试是针对主干的,因为我对这个基准的性能测试做了一些改进。但是自上次完整版本以来并没有太大变化,因此您应该会看到与0.8.1类似的结果。我还在使用我们新重写的Java producer,它比以前的 producer 客户端提供了更大的吞吐量。
我遵循了这个非常好的RabbitMQ 基准测试的基本模板,但我涵盖了与 Kafka 更相关的场景和选项。
关于这个基准的一个快速的哲学注释。对于将要公开报告的基准,我喜欢遵循一种我称之为“惰性基准”的风格。当您在一个系统上工作时,您通常具有针对任何特定用例将其调整到完美的专业知识。这会导致一种基准测试,在这种测试中,您需要根据基准对配置进行大量调整,或者更糟的是,对您测试的每个场景进行不同的调整。我认为一个系统的真正测试不是它在完美调整时的表现,而是它如何“现成”地表现。对于在具有数十个或数百个用例的多租户设置中运行的系统尤其如此,其中为每个用例进行调整不仅不切实际而且不可能。结果,我几乎坚持使用服务器和客户端的默认设置。
我已经发布了我的确切配置和命令,因此如果您有兴趣,应该可以在您自己的设备上复制结果。
硬件设置
对于这些测试,我有六台机器,每台都有以下规格
六核 Intel Xeon 2.5 GHz 处理器
六个 7200 RPM SATA 驱动器
32GB 内存
1Gb 以太网
Kafka 集群设置在三台机器上。六个驱动器直接安装,没有 RAID(JBOD 样式)。其余三台机器我用于 Zookeeper 和生成负载。
三机集群不是很大,但由于我们只测试最多三倍的复制因子,这就是我们所需要的。应该很明显,我们总是可以添加更多的分区并将数据分布到更多的机器上以水平扩展我们的集群。
这个硬件实际上并不是 LinkedIn 的普通 Kafka 硬件。我们的 Kafka 机器更接近于运行 Kafka,但不太符合我在这些测试中所追求的“现成”精神。相反,我从我们的一个 Hadoop 集群中借用了这些,它运行在我们所有持久系统中可能是最便宜的设备上。Hadoop 使用模式与 Kafka 非常相似,因此这是合理的做法。
好了,废话不多说,结果!
生产者吞吐量
这些测试将强调生产者的吞吐量。在这些测试期间没有运行消费者,因此所有消息都被持久化但不被读取(稍后我们将测试生产者和消费者的案例)。由于我们最近重写了我们的生产者,我正在测试这个新代码。
单生产者线程,无复制
821,557 条记录/秒
(78.3 MB/秒)
对于第一个测试,我创建了一个包含六个分区且没有复制的主题。然后我尽可能快地从单个线程中生成 5000 万条小(100 字节)记录。
在这些测试中关注小记录的原因是,对于消息传递系统来说(通常)更难。如果消息很大,很容易获得以 MB/sec 为单位的良好吞吐量,但当消息较小时,要获得良好的吞吐量则要困难得多,因为处理每条消息的开销占主导地位。
在整个基准测试中,当我报告 MB/秒时,我只报告每秒请求的记录值大小,不包括请求的其他开销。所以实际的网络使用率比报告的要高。例如,对于一个 100 字节的消息,我们还将为每条消息传输大约 22 个字节的开销(用于可选密钥、大小分隔、消息 CRC、记录偏移量和属性标志),以及请求的一些开销(包括主题、分区、所需的确认等)。这使得我们更难看出我们在哪里达到了 NIC 的限制,但这似乎比在吞吐量数字中包括我们自己的开销字节更合理一些。因此,在上述结果中,我们很可能使客户端机器上的 1 Gb NIC 饱和。
一个直接的观察结果是这里的原始数据比人们预期的要高得多,尤其是对于持久存储系统。如果您习惯于随机访问数据系统,例如数据库或键值存储,您通常会期望最大吞吐量约为每秒 5,000 到 50,000 个查询,因为这接近于一个好的 RPC 层可以做到的速度远程请求。由于两个关键设计原则,我们超越了这一点:
我们努力确保我们进行线性磁盘 I/O。这些服务器的六个廉价磁盘提供了 822 MB/秒的线性磁盘 I/O 总吞吐量。这实际上远远超出了我们仅使用 1 Gb 网卡所能使用的能力。许多消息传递系统将持久性视为会降低性能的昂贵附加组件,因此应谨慎使用,但这是因为它们无法进行线性 I/O。
在每个阶段,我们都致力于将少量数据批处理到更大的网络和磁盘 I/O 操作中。例如,在新的生产者中,我们使用类似“组提交”的机制来确保在另一个 I/O 正在进行时发起的任何记录发送都被组合在一起。有关了解批处理重要性的更多信息,请查看 David Patterson 关于为什么“延迟滞后带宽”的演示文稿。
如果您对细节感兴趣,您可以在我们的设计文档中阅读更多相关信息。
单生产者线程,3x 异步复制
786,980 条记录/秒
(75.1 MB/秒)
这次测试和上一次完全一样,只是现在每个分区都有三个副本(因此写入网络或磁盘的总数据是三倍)。每个服务器都在从生产者对它作为主的分区执行写入操作,以及为它作为从属的分区获取和写入数据。
此测试中的复制是异步的。也就是说,服务器在将写入写入其本地日志后立即确认写入,而无需等待其他副本也确认它。这意味着,如果主服务器崩溃,它可能会丢失最后几条已写入但尚未复制的消息。这使得消息确认延迟稍微好一点,但在服务器故障的情况下会带来一些风险。
我希望人们从中获得的关键是复制可以很快。集群的总写入容量当然要少 3 倍,复制 3 倍(因为每次写入完成 3 次),但每个客户端的吞吐量仍然相当不错。高性能复制很大程度上来自我们消费者的效率(副本实际上只不过是一个专门的消费者),我将在消费者部分讨论。
单生产者线程,3x 同步复制
421,823 条记录/秒
(40.2 MB/秒)
此测试与上面相同,只是现在分区的主服务器在向生产者确认之前等待来自完整同步副本集的确认。在这种模式下,我们保证只要保留一个同步副本,消息就不会丢失。
Kafka 中的同步复制与异步复制没有本质上的区别。分区的领导者总是跟踪跟随者副本的进度以监控它们的活跃性,并且我们永远不会向消费者发出消息,直到它们被副本完全确认。使用同步复制,我们只需等待响应生产者请求,直到追随者复制它。
这种额外的延迟似乎确实会影响我们的吞吐量。由于服务器上的代码路径非常相似,我们可以通过将批处理调整为更积极一些并允许客户端缓冲更多未完成的请求来改善这种影响。但是,本着避免特殊情况调整的精神,我避免了这种情况。
三个生产者,3x 异步复制
2,024,032 条记录/秒
(193.0 MB/秒)
我们的单一生产者进程显然没有对我们的三节点集群造成压力。为了增加一点负载,我现在将重复之前的异步复制测试,但现在使用在三台不同机器上运行的三个生产者负载生成器(在同一台机器上运行更多进程无济于事,因为我们正在使 NIC 饱和)。然后我们可以查看这三个生产者的总吞吐量,以更好地了解集群的总容量。
生产者吞吐量与存储数据
许多消息传递系统的隐藏危险之一是,只要它们保留的数据适合内存,它们就可以正常工作。当数据备份并且没有被消耗(因此需要存储在磁盘上)时,它们的吞吐量会下降一个数量级(或更多)。这意味着只要您的消费者跟上并且队列是空的,事情就可能运行良好,但是一旦它们滞后,整个消息传递层就会备份未使用的数据。备份导致数据进入磁盘,进而导致性能下降到一个速率,这意味着消息传递系统无法再跟上传入的数据,要么备份要么崩溃。这非常糟糕,因为在许多情况下,队列的全部目的是优雅地处理这种情况。
由于 Kafka 总是持久化消息,因此相对于未消耗的数据量,性能是 O(1)。
为了进行实验测试,让我们在较长时间内运行吞吐量测试,并随着存储数据集的增长绘制结果:
该图确实显示了性能上的一些差异,但由于数据大小没有影响:我们在写入 TB 数据后的性能与前几百 MB 的性能一样好。
这种差异似乎是由于 Linux 的 I/O 管理设施对数据进行批处理,然后定期刷新。这是我们在我们的生产 Kafka 设置中调整的更好一点。此处提供了有关调整 I/O 的一些说明。
消费者吞吐量
好的,现在让我们将注意力转向消费者吞吐量。
请注意,复制因子不会影响此测试的结果,因为无论复制因子如何,消费者都只会从一个副本中读取。同样,生产者的确认级别也无关紧要,因为消费者只读取完全确认的消息(即使生产者不等待完全确认)。这是为了确保消费者看到的任何消息在领导交接后始终存在(如果当前领导失败)。
单一消费者
940,521 条记录/秒
(89.7 MB/秒)
对于第一个测试,我们将在单个线程中从 6 个分区 3x 复制主题中消耗 5000 万条消息。
Kafka 的消费者非常高效。它通过直接从文件系统获取日志块来工作。它使用sendfile API直接通过操作系统传输此数据,而无需通过应用程序复制此数据的开销。这个测试实际上是从日志的开头开始的,所以它是在做真正的读 I/O。但是,在生产环境中,消费者几乎完全从操作系统页面缓存中读取,因为它正在读取刚刚由某个生产者写入的数据(因此它仍然被缓存)。事实上,如果您在生产服务器上运行 I/O stat,您实际上会看到根本没有物理读取,即使正在消耗大量数据。
让消费者便宜对于我们希望 Kafka 做的事情很重要。一方面,复制品本身就是消费者,因此让消费者便宜会使复制品便宜。此外,这使得处理数据成为一种成本低廉的操作,因此出于可扩展性的原因,我们不需要严格控制。
三个消费者
2,615,968 条记录/秒
(249.5 MB/秒)
让我们重复相同的测试,但运行三个并行的消费者进程,每个进程都在不同的机器上,并且都使用相同的主题。
正如预期的那样,我们看到了接近线性的缩放(这并不奇怪,因为我们模型中的消耗非常简单)。
生产者和消费者
795,064 条记录/秒
(75.8 MB/秒)
上述测试仅涵盖了单独运行的生产者和消费者。现在让我们做自然的事情并将它们一起运行。实际上,我们在技术上已经这样做了,因为我们的复制是通过让服务器本身充当消费者来工作的。
都一样,让我们运行测试。对于这个测试,我们将在开始为空的六个分区 3x 复制主题上运行一个生产者和一个消费者。生产者再次使用异步复制。报告的吞吐量是消费者吞吐量(显然,这是生产者吞吐量的上限)。
正如我们所预料的那样,我们得到的结果与我们在仅生产者的情况下看到的基本相同——消费者相当便宜。
消息大小的影响
我主要展示了小 100 字节消息的性能。较小的消息对于消息传递系统来说是更难的问题,因为它们会放大系统记账的开销。当我们改变记录大小时,我们可以通过以记录/秒和 MB/秒为单位绘制吞吐量来显示这一点。
因此,正如我们所料,这张图显示我们每秒可以发送的原始记录数随着记录变大而减少。但是,如果我们查看 MB/秒,我们会看到真实用户数据的总字节吞吐量随着消息变大而增加:
我们可以看到,对于 10 字节的消息,我们实际上是 CPU 绑定的,仅通过获取锁并将消息排入队列以进行发送——我们实际上无法最大化网络。然而,从 100 字节开始,我们实际上看到了网络饱和(尽管 MB/sec 继续增加,因为我们的固定大小的簿记字节在发送的总字节中所占的百分比越来越小)。
端到端延迟
2 毫秒(中位数)
3 毫秒(第 99 个百分位)
14 毫秒(第 99.9 个百分位)
我们已经谈了很多关于吞吐量的问题,但是消息传递的延迟是多少?也就是说,我们发送的消息需要多长时间才能传递给消费者?对于这个测试,我们将创建生产者和消费者,并重复计算生产者向 kafka 集群发送消息然后被消费者接收所需的时间。
请注意,Kafka 仅在完全同步的副本集确认消息时才向消费者发送消息。因此,无论我们使用同步复制还是异步复制,此测试都会给出相同的结果,因为该设置仅影响对生产者的确认。
复制此测试
如果您想在自己的机器上尝试这些基准测试,您可以。正如我所说,我大多只是使用 Kafka 附带的预打包性能测试工具,并且大多坚持使用服务器和客户端的默认配置。但是,您可以在此处查看配置和命令的更多详细信息。
sealos 以kubernetes为内核的云操作系统发行版,让云原生简单普及
共同学习,写下你的评论
评论加载中...
作者其他优质文章