一口吻说出Kafka为啥这么快?

在过去的几年里,软件架构畛域出现了渺小的变动。人们不再以为一切的系统都应该共享一个数据库。

图片来自 Pexels

微服务、事情驱动架构和 CQRS(命令查问的责任分别 Command Query ResponsibilitySegregation)是构建当代业务运行程序的关键工具。

除此以外,物联网、移动设施和可穿戴设施的遍及,进一步对系统的近实时才干提出了应战。

首先让咱们对“快”这个词达成共识,这个词是多方面的、复杂的、高度含糊的。一种解释是把”提早、吞吐量和颤抖“作为对“快”的权衡目的。

还有,比如工业运行畛域,行业自身设置了关于“快”的规范和希冀。所以,“快”在很大水平上取决于你的参照体系是什么。

Apache Kafka 以就义提早和颤抖为代价提升了吞吐量,但并没有就义,比如耐久性、严厉的记载有序性和至少一次性的散发语义。

当有人说“Kafka 速度很快”,并假定他们至少有必定的才干时,你可以以为他们指的是 Kafka 在短时期内散发少量记载的才干。

Kafka 降生于 LinkedIn,过后 LinkedIn 须要高效地传递少量信息,相当于每小时传输数 TB 的数据量。

在过后,信息流传的提早被以为是可以接受的。毕竟,LinkedIn 不是一家从事高频买卖的金融机构,也不是一个在确活期限内运转的工业控制系统。Kafka可用于近实时系统。

留意:“实时”并不象征着“快”,它的意思是“可预测的”。详细来说,实时象征着成功一个举措具备时期限度,也就是最前期限。

假设一个系统不能满足这个要求,它就不能被归类为”实时系统“。能够容忍必定范围内提早的系统被称为“近实时”系统。从吞吐量的角度来说,实时系统通常比近实时或非实时系统要慢。

Kafka 在速度上有两个关键的方面,须要独自探讨:

服务端提升

日志的存储

Kafka 应用分段、追加日志的方式,在很大水平上将读写限度为顺序 I/O(sequentialI/O),这在大少数的存储介质上都很快。人们广泛失误地以为硬盘很慢。

但是,存储介质的性能,很大水平上依赖于数据被访问的形式。雷同在一块个别的 7200 RPM SATA 硬盘上,随机 I/O(random I/O)与顺序I/O 相比,随机 I/O 的性能要比顺序 I/O 慢 3 到 4 个数量级。

此外,现代的操作系统提供了预先读和提早写的技术,这些技术可以以块为单位,预先读取少量数据,并将较小的逻辑写操作兼并成较大的物理写操作。

因此,顺序 I/O 和随机 I/O之间的性能差异在闪存和其余固态非易失性介质中依然很显著,不过它们在旋转存储,比如固态硬盘中的性能差异就没有那么显著。

记载的批处置

顺序 I/O 在大少数存储介质上都十分快,可以与网络 I/O的最高性能相媲美。在通常中,这象征着一个设计良好的日志耐久化层能跟上网络的读写速度。理想上,Kafka 的性能瓶颈通常并不在硬盘上,而是网络。

因此,除了操作系统提供的批处置外,Kafka 的客户端和服务端会在一个批处置中积攒多个记载——包含读写记载,而后在经过网络发送出去。

记载的批处置可以缓解网络往复的开支,经常使用更大的数据包,提高带宽的效率。

批量紧缩

当启用紧缩时,对批处置的影响特意显著,由于随着数据大小的参与,紧缩通常会变得更有效。

特意是在经常使用基于文本的格局时,比如 JSON,紧缩的成果会十分显著,紧缩比通常在 5x 到 7x 之间。

此外,记载的批处置关键作为一个客户端操作,负载在传递的环节中,不只对网络带宽有踊跃影响,而且对服务端的磁盘 I/O 应用率也有踊跃影响。

廉价的消费者

不同于传统的信息队列模型,当信息被消费时会删除信息(会造成随机 I/O),Kafka不会在信息被消费后删除它们——相反,它会独立地跟踪每个消费者组的偏移量。

可以参考 Kafka 的外部主题 __consumer_offsets 了解更多。雷同,由于只是追加操作,所以速度很快。信息的大小在后盾被进一步缩小(经常使用Kafka 的紧缩特性),只保管任何给定消费者组的最后已知偏移量。

将此模型与传统的信息模型启动对比,后者通常提供几种不同的信息散发拓扑。

一种是信息队列——用于点对点信息传递的耐久化传输,没有点对多点性能。

另一种是颁布订阅主题准许点对多点信息通讯,但这样做的代价是耐久性。在传统信息队列模型中成功耐久化的点对多点信息通讯模型须要为每个有形态的经常使用者保养公用信息队列。

这将加大读写的消耗。信息消费者自愿将信息写入多个信息队列中。另外一种选用是经常使用扇出中继,扇出中继可以消费来自一个队列中的记载,并将记载写入其余多个队列中,但这只会将提早加大点。

并且,一些消费者正在服务端上生成负载——读和写 I/O 的混合,既有顺序的,也有随机的。

Kafka 中的消费者是“廉价的”,只需他们不扭转日志文件(只要消费者或 Kafka 的外部进程被准许这样做)。

这象征着少量消费者可以并发地从同一主题读取数据,而不会使集群解体。参与一个消费者依然有一些老本,但关键是顺序读取夹杂很少的顺序写入。

因此,在一个多样化的消费者系统中,看到一个主题被共享是相当反常的。

未刷新的缓冲写操作

Kafka 性能的另一个基本要素是,一个值得进一步钻研的要素:Kafka 在确认写操作之前并没有调用 fsync。ACK 的惟一要求是记载曾经写入 I/O缓冲区。

这是一个不为人知的理想,但却是一个至关关键的理想。实践上,这就是 Kafka 的口头方式,就如同它是一个内存队列一样——Kafka实践上是一个由磁盘支持的内存队列(受缓冲区/页面缓存大小的限度)。

但是,这种方式的写入是不安保的,由于正本的出错或许造成数据失落,即使记载似乎曾经被 ACK。

换句话说,与相关型数据库不同,仅写入缓冲区并不象征着耐久性。保障 Kafka 耐久性的是运转几个同步的正本。

即使其中一个出错了,其余的(假定不止一个)将继续运转——假定出错的要素不会造成其余的正本也出错。

因此,无 fsync 的非阻塞 I/O 方法和冗余的同步正本组合为 Kafka 提供了高吞吐、耐久性和可用性。

客户端提升

大少数数据库、队列和其余方式的耐久性两边件都是围绕全能主机(或主机集群)和瘦客户端的概念设计的。

客户端的成功通常被以为比主机端便捷得多。主机会处置大局部的负载,而客户端仅充任服务端的门面。

Kafka 驳回了不同的客户端设计方法。在记载抵达主机之前,会在客户端上口头少量的上班。

这包含对累加器中的记载启动分段、对记载键启动散列以失掉正确的分区索引、对记载启动校验以及对记载批处置启动紧缩。

客户端知道集群元数据,并活期刷新元数据以跟上服务端拓扑的更改。这让客户端更准确的做出转发决策。

不同于自觉地将记载发送到集群并依托后者将其转发到适当的节点,消费者客户端可以直接将写恳求转发到分区主机。

相似地,消费者客户端能够在失掉记载时做出更理智的选择,比如在收回读查问时,可以经常使用在天文上更凑近消费者客户端的正本。(该特性是从 Kafka 的2.4.0 版本开局提供。)

零拷贝

一种典型的低效方式是在缓冲之间复制字节数据。Kafka经常使用由消费者、消费者、服务端三方共享的二进制信息格局,这样即使数据块被紧缩了,也可以不加修正地传递数据。

虽然消弭通讯方之间的数据结构差异是关键的一步,但它自身并不能防止数据的复制。

Kafka 经常使用 Java 的 NIO 框架,特意是 java.nio.channels.FileChannel 的 transferTo() 方法,在Linux 和 UNIX 系统上处置了这个疑问。

此方法准许字节从源通道传输到接纳通道,而不须要将运行程序作为传输中介。

了解 NIO 的不同之处,请思索传统的方法会怎样做,将源通道读入字节缓冲区,而后作为两个独立的操作写入接纳器通道:

可以用下图来示意:

虽然这副图看起来很便捷,但是在外部,复制操作须要在用户态和内核态之间启动四次高低文切换,并且在操作成功之前要复制四次数据。

下图概述了每次步骤的高低文切换:

详细说明:

虽然用户态与内核态之间的高低文切换效率很低,而且还须要启动额外的复制,但在许多状况下,它可以提高性能。

它可以充任预读缓存,异步预读取,从而提早运转来自运行程序的恳求。但是,当恳求的数据量远远大于内核缓冲区的大小时,内核缓冲区就成为了性能瓶颈。

不同于直接复制数据,而是迫使系统在用户态和内核态之间频繁切换,直到一切数据都被传输。

相比之下,零拷贝方法是在单个操作中处置的。前面例子中的代码可以改写为一行代码:

上方详细解释说明是零拷贝:

在这个模型中,高低文切换的数量缩小到一个。详细来说,transferTo() 方法批示块设施经过 DMA 引擎将数据读入读缓冲区。

而后,将数据从读缓冲区复制到套接字缓冲区。最后,经过 DMA 将数据从套接字缓冲区复制到 NIC 缓冲区。

因此,咱们将复制的数量从 4 个缩小到 3 个,并且其中只要一个复制操作触及到 CPU。咱们还将高低文切换的数量从 4 个缩小到 2 个。

这是一个渺小的改良,但还不是查问零拷贝。在运转 Linux 内核 2.4 或更高版本时,以及在支持 gather 操作的网卡上,可以进一步提升。

如下图所示:

依照前面的示例,调用 transferTo() 方法会造成设施经过 DMA 引擎将数据读入内核缓冲区。

但是,关于 gather 操作,读缓冲区和套接字缓冲区之间不存在复制。相反,NIC被赋予一个指向读缓冲区的指针,连同偏移量和长度。在任何状况下,CPU都不触及复制缓冲区。

文件大小从几 MB 到 1GB 的范围内,传统拷贝和零拷贝相比,结果显示零拷贝的性能提高了两到三倍。

但更令人印象深入的是,Kafka 经常使用纯 JVM 成功了这一点,没有本地库或 JNI 代码。

防止渣滓回收

少量经常使用通道、缓冲区和页面缓存还有一个额外的好处——缩小渣滓搜集器的上班负载。

例如,在 32 GB RAM 的机器上运转 Kafka 将发生 28-30 GB 的页面缓存可用空间,齐全超出了渣滓搜集器的范围。

吞吐量的差异十分小(大概几个百分点),但是经过正确调优的渣滓搜集器的吞吐量或许十分高,特意是在处置短生活期对象时。真正的收益在于缩小颤抖。

经过防止渣滓回收,服务端不太或许遇到因渣滓回收惹起的程序暂停,从而影响客户端,加大记载的通讯提早。

与初期的 Kafka 相比,如今防止渣滓回收曾经不是什么疑问了。像 Shenandoah 和 ZGC 这样的现代渣滓搜集器可以裁减到渺小的、多 TB级的堆,在最坏的状况下,并且可以智能调整渣滓搜集的暂停时期,降到几毫秒。

如今,可以看见少量的基于 Java 虚构机的运行程序经常使用堆缓存,而不是堆外缓存。

流处置的并行性

日志的 I/O 效率是性能的一个关键方面,关键的性能影响在于写。Kafka 对主题结构和消费生态系统中的并行性处置是其读性能的基础。

这种组合发生了全体十分高的端到端信息吞吐量。将并发性深化到分区打算和经常使用者组的操作中,这实践上是 Kafka中的一种负载平衡机制——将分区平均地调配到各个消费者中。

将此与传统的信息队列启动比拟:在 RabbitMQ 的设置中,多个并发的消费者可以以轮询的方式从队列中读取数据,但这样做会丧失信息的有序性。

分区机制无利于 Kafka 服务端的水平裁减。每个分区都有一个专门的指导者。因此,任何关键的多分区的主题都可以应用整个服务端集群启动写操作。

这是 Kafka 和传统信息队列的另一个区别。当后者应用集群来提高可用性时,Kafka 经过负载平衡来提高可用性、耐久性和吞吐量。

颁布具备多个分区的主题时,消费者指定颁布记载时的分区。(或许有一个单分区主题,那就不是疑问了)

可以经过指定分区索引直接成功,或经过记载键直接成功,记载键经过计算散列值确定分区索引。具备相反散列值的记载共享相反的分区。

假定一个主题有多个分区,那么具备不同键的记载或许会出如今不同的分区中。

但是,由于散列抵触,具备不同散列值的记载也或许最终出如今同一个分区中。这就是散列的实质。假设你了解了散列表的上班方式,一切都很人造了。

记载的实践处置由消费者成功,在一个可选的消费者组中成功。Kafka保障一个分区最多只能调配给消费者组中的一个消费者。(为什么用”最多“,当一切消费者都离线时,那就是 0 个消费者了)

当组中的第一个消费者订阅主题时,它将接纳该主题上的一切分区。当第二个消费者订阅主题时,它将接纳到大概一半的分区,从而减轻第一个消费者的负载。

依据须要参与消费者(理想状况下,经常使用智能伸缩机制),这使你能够并行地处置事情流,前提是你曾经对事情流启动了分区。

以两种方式控制记载的吞吐量:

①主题分区打算 。应该对主题启动分区,最大化事情流的数量。换句话说,只要在相对须要时才提供记载的顺序。

假设任何两个记载不存在关联,它们就不应该被绑定到同一个分区。这象征着要经常使用不同的键,由于 Kafka 经常使用记载键的散列值作为分区映射的依据。

②组中消费者的数量 。你可以参与消费者的数量来平衡入站记载的负载,消费者的数量最多可以参与到和分区数量一样多。(你可以参与更多的消费者,但每个分区最多只能有一个的优惠消费者,剩下的消费者将处于闲置形态)

请留意,你可以提供一个线程池,依据消费者口头上班负载的不同,消费者可以是一个进程或一个线程。

假设你想知道 Kafka 为什么这么快,它是如何做到的,以及它能否适宜你,我想你如今曾经有了答案了。

为了更清楚地说明疑问,Kafka 不是最快的信息两边件,吞吐量也不是最大的。有其余平台能够提供更高的吞吐量——有些是基于软件的,有些是基于配件的。

很难同时做到吞吐量大且提早低,Apache Pulsar[1] 是一个有出路的技术,可裁减,更好的吞吐量-提早性能文件,同时提供顺序性和耐久性。

驳回 Kafka 的理由是,作为一个完整的生态系统,它在全体上依然是无可比拟的。

它展现了杰出的性能,同时提供了一个丰盛和成熟的环境,Kafka 仍在以令人艳羡的速度增长。

Kafka 的设计者和保养者在设计一个以性能为外围的处置打算时做了少量的上班。它的设计元素中很少有让人觉得是预先才想到的,或许是补全的。

从将上班负载转移到客户端,到服务端日志的耐久性、批处置、紧缩、零拷贝 I/O 和并行流处置——Kafka向任何其余信息两边件厂商动员应战,无论是商业的还是开源的。

最令人印象深入的是,它做到了这一点,却没有就义耐久性、记载有序性和至少一次性散发的语义。

Kafka不是最便捷的信息两边件平台,还有许多须要改良的中央。在设计和构建高性能事情驱动系统之前,必定把握总体和局部的顺序、主题、分区、消费者和消费者组的概念。

虽然常识曲线很峻峭,但值得你花时期去学习。假设你知道这个谚语“red pill”(redpill,指为了到达对某种事物的深度探求或谋求,选用去思索,不丢弃,继续走下去,哪怕这条路多难走),请浏览“引见 Kafka 和 Kafdrop 中的事情流Introduction to Event Streaming with Kafka and Kafdrop[2]”。

相关链接:

您可能还会对下面的文章感兴趣: