Kafka: 用于日志处理的分布式消息系统

本文为《Kafka: a Distributed Messaging System for Log Processing》的全文翻译,为了让大家原汁原味的领悟到kafka的设计精髓,所以采用直译并适当在括号中添加了作者观点。
本文为作者原创文章,转载请保留出处和链接

摘要

日志处理已成为消费者互联网公司数据通道的一个关键组成部分。我们介绍卡夫卡(Kafka),一种为了在低延迟下收集和传递大容量的日志数据而开发的分布式消息系统。我们的系统融合了现有的日志聚合器和消息系统的想法,适用于离线和在线消息消费。我们在Kafka做了一系列非常规但实用的设计来确保我们的系统有效性和扩展性。我们的实验结果表明,Kafka在和另外两款流行的消息系统的对比展现出了优秀的性能。我们已经在生产中使用Kafka已有一段时间了,它每天要处理数百GB的新数据。

概述

管理,性能,设计,实验。

关键词

消息,分布式,日志处理,吞吐率,在线

1. 引言

任何大型互联网企业都会生成海量的日志数据。这些数据通常与(1)用户活动事件对应,如登录,页面浏览,点击,点赞,分享,评论和搜索;(2)操作指标,例如服务调用栈,调用延迟,错误和系统指标(如每台服务器上的CPU,内存,网络或磁盘使用率)。日志数据长期以来被当做一种分析组件去追踪用户粘度,系统使用率和其他指标。然而,互联网应用的最新趋势是直接在在站点中使用活动数据(一部分的生产数据)。这些包括(1)搜索关联,(2)推荐由广受欢迎的项目和事件驱动的内容(3)广告跟踪和报告,(4)防止和对抗滥用行为的安全应用程序,例如垃圾邮件或未经授权的数据抓取,以及(5)聚合用户状态更新,供他们的功能“朋友”或“人脉”阅读。

日志数据的这种实时生产使用给数据系统带来了新的挑战,因为其数据量比“实际”数据大几个数量级。 例如,搜索,推荐和广告通常需要精细的计算点击率,这不仅会为每个用户点击生成日志记录,还会为每个页面上数十个未被点击的项目生成日志记录。 每天,中国移动收集5–8 TB的通话记录[11],而Facebook收集近6 TB的各种用户活动事件[12]。

许多用于处理此类数据的早期系统都依赖于从生产服务器上剥离日志数据(scraping log files off production servers)来进行分析。许多用于处理此类数据的早期系统都依靠物理方式将日志文件从生产服务器上刮下来进行分析。近年来,已经建立了几种专门的分布式日志聚合器,包括Facebook的Scribe [6],Yahoo的Data Highway [4]和Cloudera的Flume [3]。这些系统主要用于收集日志数据并将其加载日志数据到数据仓库或Hadoop [8](数据分析工具)中以供离线使用。在LinkedIn(一个社交网站)上,我们发现,除了上述传统的离线分析,我们还需要以不超过几秒钟的延迟来支持大多数准实时应用程序。

我们已经建立了一种用于日志处理的新型消息传递系统,称为Kafka [18],该系统结合了传统日志聚合器和消息系统的优势。 一方面,Kafka是分布式且可扩展的,并提供高吞吐量。 另一方面,Kafka提供类似于消息传递系统的API,并允许应用程序实时消费(consume)日志事件。 Kafka已开源,并已在LinkedIn上成功用于生产中超过6个月。它极大地简化了我们的服务架构,因为我们可以利用单个软件来消费所有类型的在线和离线数据,所以。本文的其余部分如下。 我们将在第2节中回顾传统的消息传递系统和日志聚合器。在第3节中,我们描述Kafka的体系结构及其关键设计原则。我们将在第4节中介绍在LinkedIn上部署Kafka的情况,并在第5节中介绍Kafka的性能结果。在第6节中,我们讨论未来的工作并得出结论。

2. 相关工作

传统的企业级消息传递系统[1] [7] [15] [17]已经存在了很长时间,并且总是在处理异步数据流的事件总线发挥关键作用。但是,由于某些原因,它们往往不适合日志处理。首先,有一些不匹配的特征被提供给这些企业级系统。这些系统通常专注于提供丰富的传递保证(delivery guarantees, 这里可以理解为传递特性)。例如,IBM Websphere MQ [7]支持事务,允许应用程序原子地将消息插入多个队列。 JMS [14]规范允许在每条消息被消费后才确认(译者注: 消息消费后才提交消费位移),这可能会导致乱序消费(译者注: 分布式系统里面并行消费消息不能保证消费的顺序)。这样的交付保证对于收集日志数据通常是过大的。例如,偶尔失去一些综合浏览量事件肯定不是世界末日。这些不需要的功能往往会增加API和这些系统的基础实现的复杂性。其次,许多系统没有极力的专注于吞吐量,把它主要的设计约束。例如,JMS没有API允许生产者将多个消息显式批处理到一个单个请求。这意味着每个消息都需要完整的TCP / IP往返,这对于我们领域的吞吐量要求是不可行的。第三,这些系统缺乏分布式支持。没有简单的方法可以在多台计算机上分区和存储消息。最后,许多消息传递系统假定消息将立即消耗掉,因此未消费消息的队列总是很小。如果允许堆积消息,则它们的性能将大大降低,对于离线消费者不合适(例如,数据仓库会定期进行高负载而不是连续消费)。

在过去的几年中,已经建立了许多专业的日志聚合器。 Facebook使用名为Scribe的系统。每个前端计算机都可以通过套接字将日志数据发送到一组Scribe服务器。每台Scribe机器都会汇总日志条目,并将它们定期转储(periodically dumps)到HDFS [9]或NFS设备。雅虎的数据高速公路项目具有类似的数据流。一组计算机聚集来自客户端的事件并获得(roll out)“分钟”文件,然后将其添加到HDFS。 Flume是由Cloudera开发的相对较新的日志聚合器。它支持可扩展的“管道”和“接收器”,并使流式日志数据非常灵活。它还具有更多集成的分布式支持。但是,大多数这些系统是为脱机使用日志数据而构建的,并且经常不必要地向消费者(译者注: 原文是consumer,指的是消息消费方)公开实现细节(例如“分钟文件”)。此外,它们中的大多数使用“推”模型把数据从broker转发给消费者。在LinkedIn,我们发现“拉”模型更适合我们的应用程序,因为每个消费者都可以以其可以维持的最大速率取回(retrieve)消息,并避免推送速度超出其处理能力。拉模型还可以轻松地倒带(指重复消费),我们将在第3.2节的末尾讨论这种好处。最近,雅虎!研究开发了一种新的分布式发布/订阅系统,称为HedWig [13]。 HedWig具有高度的可扩展性和可用性,并提供强大的高可用(strong durability)保证。然后,它主要用于存储数据存储的提交日志。

3.卡夫卡架构与设计原则

由于现有系统的限制,我们开发了一种新的基于消息的日志聚合器Kafka。我们首先介绍Kafka中的基本概念。一个特定类型的消息流被定义为一个主题。生产者可以将消息发布到主题。 然后,已发布的消息将存储在一组称为broker的服务器上。 消费者可以订阅来自brokers的一个或多个主题,并通过从brokers上拉数据来消费订阅的消息。

消息从概念上讲很简单,我们试图使Kafka API同样简单以反映这一点。 我们没有显示确切的API,而是提供了一些示例代码来说明如何使用API。 生产者的示例代码如下。 一条消息被定义为仅包含有效负载(payload)的字节。用户可以选择她喜欢的序列化方法来编码消息。 为了提高效率,生产者可以在单个发布请求中发送一组消息(批量发送)。

发送代码样例:

producer = new Producer(…);
message = new Message(“test message str”.getBytes()); 
set = new MessageSet(message);
producer.send(“topic1”, set);

为了订阅主题,消费者首先为该主题创建一个或多个消息流。 发布给该主题的消息将均匀分配到这些子流中。有关Kafka如何分发消息的详细信息,将在第3.2节中介绍。每个消息流为不断产生的消息上提供迭代器接口。然后,消费者迭代流中的每个消息并处理消息的有效负载。与传统的迭代器不同,消息流迭代器永远不会终止。如果当前没有更多消息可使用,则迭代器将阻塞,直到将新消息发布到该主题为止。我们既支持多个消费者共同使用一个主题中所有消息的单个副本的点对点传递模型,也支持多个消费者各自检索其自己的主题副本的发布/订阅模型。

消费代码样例:

streams[] = Consumer.createMessageStreams(“topic1”, 1);
for (message : streams[0]) {
bytes = message.payload();
// do something with the bytes
}

Kafka的总体结构如图1所示。由于Kafka实际上是分布式的,因此一个Kafka集群通常由多个代理(brokers)组成。为了负载均衡,一个主题被分为多个分区,每个代理存储一个或多个这些分区。多个生产者和消费者可以同时发布和取回消息。 在第3.1节中,我们描述了代理上单个分区的布局以及我们选择的一些设计选择,以使访问分区更加有效。在3.2节中,我们描述了生产者和消费者如何在分布式环境中与多个代理进行交互。 我们将在第3.3节中讨论Kafka的传输保障。

图1. Kafka架构
图1. Kafka架构

3.1单分区有效性

我们在Kafka上做出了一些决策来提高系统效率。

简单存储:Kafka的存储布局非常简单。主题的每个分区都对应一个逻辑日志。在物理上,日志是由一组大小近似相同(例如1GB)的段文件实现的。每次生产者将消息发布到分区时,代理都将消息简单地附加到最后一个段文件。为了获得更好的性能,我们仅在发布了可配置数量的消息或经过一定时间后才将分段文件刷新到磁盘。消息仅在刷新后才暴露(exposed)给消费者。(译者注:其实对应了kafka刷盘时候的两个参数,超过一定数量,超过一定时间触发刷盘策略,其实网络IO发送接收数据也是这样,提供缓冲来提高性能)
与典型的邮件系统不同,存储在Kafka中的邮件没有显式(explicit)的消息ID。而是每个消息都通过其在日志中的逻辑偏移量(offset)来寻址。这样避免了维护将消息ID映射到实际消息位置的过程中,密集的随机访问索引产生的开销。请注意,我们的消息ID正在增加,但不是连续的。要计算下一条消息的ID,我们必须将当前消息的长度添加到其ID中。从现在开始,我们将交替使用消息ID和偏移量。

消费者始终按顺序使用来自特定分区的消息。如果消费者确认特定的消息偏移量,则表示消费者已经在分区中的该偏移量之前接收了所有消息。在这个过程的背后,消费者正在向代理发送异步请求,以使数据缓冲区可供应用程序使用。每个拉取请求都包含从中开始使用的消息的偏移量和要提取的可接受的字节数(译者注: 这里可以参考消息的结构)。每个代理将内存中的偏移量列表排序,包括每个段文件中第一条消息的偏移量。代理通过搜索偏移量列表来定位所请求消息所保存的段文件,并将数据发送回消费者。在消费者接收到一条消息后,它将计算下一条要使用的消息的偏移量,并在下一个请求请求中使用它(译者注:这里指的是提交消费位移,是当前消费位移+1)。Kafka日志和内存索引的布局如图2所示。每个框显示一条消息的偏移量。
图2 卡夫卡日志结构
图2 卡夫卡日志结构

高效传输:我们非常谨慎地将数据传入和传出Kafka。之前,我们已经展示了生产者可以在单个发送请求中提交一组消息。尽管最终用户API一次迭代一条消息,但在幕后,来自用户的每个拉取请求还可以取回(retrieves)到一定大小(通常为数百KB)的多条消息。

我们做出的另一个非常规选择是避免在Kafka层上将消息显式缓存在内存中。相反,我们依赖于基础文件系统页面缓存(pagecache)。这避免了双重缓冲消息。这样做还有一个好处,即使重新启动代理进程,也可以保留热缓存。由于Kafka根本不缓存进程中的消息,因此在垃圾回收(GC)方面几乎没有开销,因此可以使用基于VM的语言进行高效实现。最后,由于生产者和消费者都可以顺序地访问段文件,由于消费者通常落后于生产者少量,因此正常的操作系统缓存试探法非常有效(特别是直写式缓存和预读)。我们已经发现,生产和消耗都具有与数据大小成线性关系的一致性能,最大可达数TB的数据。

此外,我们为消费者优化了网络访问。 Kafka是一个多用户系统,单个消息可能会被不同的消费者应用程序多次使用。从本地文件向远程套接字发送字节的典型方法包括以下步骤:(1)从存储介质读取数据到OS中的页面缓存,(2)将页面缓存中的数据复制到应用程序缓冲区,(3)将应用程序缓冲区复制到另一个内核缓冲区,(4)将内核缓冲区发送到套接字。这包括4个数据复制和2个系统调用。在Linux和其他Unix操作系统上,存在一个sendfile API [5],它可以直接将字节从文件通道传输到套接字通道。这样通常可以避免步骤2和步骤3中引入2个副本和1个系统调用。 Kafka利用sendfile API有效地将日志段文件中的字节从代理传递到消费者。(译者注:这里用了零拷贝技术,避免了拷贝过程中无意义的数据复制和Linux系统中内核态和用户态的切换)

无状态代理:与大多数其他消息传递系统不同,在Kafka中,有关每个消费者已消费多少的信息不是由代理维护的,而是由消费者自己维护的。这样的设计减少了代理的很多复杂性和开销。但是,由于代理不知道是否所有订阅者都已消费了消息,因此删除变得很棘手(tricky)。 Kafka通过将简单的基于时间的SLA用于保留策略来解决此问题。如果消息在代理中的保留时间超过一定时间(通常为7天),则会自动删除该消息。 该解决方案在实践中效果很好。 无论大多数消费者(包括离线消费者)每天,每小时或实时完成消费。Kafka的性能不会随着数据量的增加而降低,这一事实使得这种长期保留成为可能。

此设计有一个重要的附带好处。 消费者可以有意地倒回旧的偏移量并重新使用数据。 这违反了队列的通用约定,但是事实证明这是许多消费者的基本功能。 例如,当消费者中的应用程序逻辑出现错误时,错误修复后,应用程序可以重播某些消息。 这对于将ETL数据加载到我们的数据仓库或Hadoop系统中特别重要。 另一个例子是,所消费的数据可以仅周期性地被刷新到持久性存储(例如,全文本索引器)。如果消费者崩溃了,未清除的数据将会丢失。(译者注:kafka的leader刷盘成功就返回的话,ISR副本没有fetch数据的情况下,如果leader硬盘坏丢数据,数据会产生少量丢失)在这种情况下,消费者可以检查未清除消息的最小偏移量,并在重新启动后从该偏移量中重新使用。我们注意到,在拉模型中支持消费者重复消费要比推模型容易得多。

3.2分布式协调

现在我们描述生产者和消费者在分布式环境中的行为。每个生产者可以将消息发布到随机选择的分区或由分区键和分区函数(译者注:这里和rabbitmq的路由键不一样)语义确定的分区。我们将关注消费者与代理的互动方式。

Kafka具有消费群体的概念。每个消费者组由一个或多个共同组成一组订阅主题的消费者组成,即,每个消息仅传递给该组中的一个消费者。(译者注:集群消费)不同的消费者组各自独立地消费整个订阅消息集,并且不需要跨消费者组进行协调。同一组中的消费者可以处理不同的消息,或者在不同的机器上。我们的目标是在消费者之间平均分配存储在代理中的消息,而不会引入过多的协调开销。(译者注:消费者负载均衡策略)

我们的第一个决定是使主题内的分区成为并行度的最小单位。这意味着在任何给定时间,来自一个分区的所有消息仅由每个消费者组中的单个消费者使用。如果我们允许多个消费者同时使用一个分区,那么他们将必须协调谁使用哪些消息,这需要锁定和状态维护开销。相反,在我们的设计中,消耗过程仅在消耗者重新平衡负载时才需要协调,这种情况很少发生。为了使负载真正达到平衡,我们在一个主题中需要的分区要比每组中的消费者多得多。我们可以通过对主题进行过度划分来轻松实现这一目标。(译者注:队列轻量化)
我们做出的第二个决定是没有中央的“主”节点,而是让消费者以分散的方式在彼此之间进行协调。添加主服务器会使系统复杂化,因为我们必须进一步担心主服务器故障。为了促进协调,我们使用了高度可用的共识服务Zookeeper [10]。 Zookeeper有一个非常简单的文件系统,例如API。可以创建路径,设置路径的值,读取路径的值,删除路径和列出路径的子路径。它做一些更有趣的事情:(a)可以在路径上注册观察者(watcher),并在路径的子代或路径的值发生更改时得到通知; (b)可以临时创建一条路径(与持久性相反),这意味着如果创建的客户端不存在,则该路径将由Zookeeper服务器自动删除; (c)zookeeper将其数据复制到多个服务器,这使数据高度可靠且可用。

Kafka使用Zookeeper执行以下任务:(1)检测代理和消费者的添加和删除,(2)在上述事件发生时在每个消费者中触发重新平衡过程,以及(3)维护消费关系并跟踪每个分区的消耗的偏移量。具体来说,每个代理或消费者启动时,会将其信息存储在Zookeeper中的代理或消费者注册表中。代理注册表包含代理的主机名和端口,以及存储在其中的主题和分区集。消费者注册表包括消费者所属的消费者组及其订阅的主题集。每个消费者组都与Zookeeper中的所有权注册表和偏移量注册表关联。注册表对每个订阅的分区都有一个路径,路径值是该分区当前使用的消费者的ID(我们使用消费者拥有该分区的术语)。偏移量注册表为每个订阅的分区存储该分区中最后消耗的消息的偏移量。(译者注:kafka为每个分区在zk上注册对应的数据并管理偏移量)

在Zookeeper中创建的路径对于代理注册表,消费者注册表和所有权注册表是临时的,对于偏移量注册表是持久的。如果代理失效,则该代理上的所有分区都会自动从代理注册表中删除。消费者的故障导致它丢失在消费者注册表中的条目以及在所有者注册表中拥有的所有分区。每个消费者都在代理注册表和消费者注册表上都注册了Zookeeper监视程序,并且每当代理集或消费者组发生更改时,都将收到通知。(译者注:故障转移策略,kafka在0.8.x之后引入了controller来观察zk,避免了集群行为不一致,即脑裂)

在消费者的初始启动过程中,或者通过观察者通知消费者有关代理/消费者更改的消息时,消费者将启动重新平衡过程以确定应从中使用的新分区子集。算法1中描述了该过程。通过从Zookeeper中读取代理和消费者注册表,消费者首先计算可用于每个已订阅主题T的分区集(PT)和订阅T的消费者集(CT)。对于消费者选择的每个分区,它都会在偏移量注册表将自己写为该分区的新所有者。最后,消费者开始一个线程,从偏移注册表中存储的偏移开始,从每个拥有的分区中提取数据。随着从分区中提取消息,消费者将定期更新偏移量注册表中的最新消耗的偏移量。(译者注:消费者上下线引发负载均衡)

当一个组中有多个消费者时,将通知每个代理或消费者变更。但是,在消费者处通知的发出时间可能略有不同。因此,一个消费者有可能尝试获得仍由另一个消费者拥有的分区的所有权。发生这种情况时,第一个消费者只需释放其当前拥有的所有分区,稍等片刻,然后重试重新平衡过程。实际上,重新平衡过程通常只需重试几次即可稳定下来。(译者注:队列的所有者切换)

创建新的消费者组时,偏移量注册表中没有可用的偏移量。在这种情况下,使用我们在代理程序上提供的API,使用方将从每个订阅分区上可用的最小或最大偏移量(取决于配置)开始。

3.3交付保证

通常,Kafka仅保证最少一次交付(at least once)。仅一次交付通常需要两阶段提交,而对于我们的应用程序则不是必需的。在大多数情况下,一条消息仅一次发送给每个消费者组。但是,如果消费者进程崩溃而没有完全关闭,则接管有故障的消费者拥有的那些分区的消费者进程可能会得到一些重复的消息,这些消息是在最后一次偏移成功提交给Zookeeper之后的。如果应用程序关心重复项,则它必须使用我们返回给消费者的偏移量或消息中的某些唯一键来添加自己的重复数据删除逻辑。与使用两阶段提交相比,这通常是一种更具成本效益的方法。(译者注:目前大部分应用都是通过flink或者db来保证精确一次)
Kafka保证将来自单个分区的消息按顺序传递给消费者。但是,不能保证来自不同分区的消息的顺序。
为了避免日志损坏,Kafka将每个消息的CRC存储在日志中。如果代理上存在任何I / O错误,Kafka将运行恢复过程以删除带有不一致CRC的消息。在消息级别使用CRC还可以使我们在产生或使用消息之后检查网络错误。

如果代理发生故障,则存储在其上尚未使用的任何消息将变得不可用。如果代理上的存储系统被永久损坏,则所有未使用的消息将永远丢失。将来,我们计划在Kafka中添加内置复制,以将每个消息冗余地存储在多个代理上。

4. LinkedIn上的Kafka的使用

在本节中,我们描述了如何在LinkedIn使用Kafka。图3显示了我们部署的简化版本。我们有一个Kafka集群与每个运行面向用户服务的数据中心共置一处。前端服务生成各种日志数据,并将其分批发布到本地Kafka代理。我们依靠硬件负载平衡器将发布请求平均分配给Kafka brokers。 Kafka的在线消费者在同一数据中心内的服务中运行。
在这里插入图片描述
我们还在单独的数据中心中部署了一个Kafka集群,以进行离线分析,该集群的地理位置靠近我们的Hadoop集群和其他数据仓库基础架构。该Kafka实例运行一组嵌入式(embedded)消费者,以从实时数据中心的Kafka实例中提取数据。然后,我们运行数据加载作业,以将数据从Kafka的副本集群中拉入Hadoop和我们的数据仓库,在此我们对数据运行各种报告作业和分析过程。我们还使用此Kafka集群进行原型制作,并能够针对原始事件流运行简单脚本以进行临时查询。无需太多调整,整个管道的端到端延迟平均约为10秒,足以满足我们的要求。

目前,Kafka每天存储数百GB的数据和近10亿条消息,随着我们完成对旧系统的转换以利用Kafka的优势,我们预计这一数字将显着增长。将来会添加更多类型的消息。当操作人员启动或停止代理进行软件或硬件维护时的重新平衡过程能够自动重定向。(译者注:这里原文写的有点别扭,应该是说集群可以自己迁移分区,实现整个集群的负载均衡)

我们的跟踪还包括一个审计系统,以验证整个管道中没有数据丢失。为方便起见,每条消息在生成时均带有时间戳和服务器名称。我们对每个生产者进行检测,使其定期生成监视事件,该事件记录该生产者在固定时间窗口内针对每个主题发布的消息数。生产者在单独的主题中将监视事件发布到Kafka。然后,消费者可以计算他们从给定主题中收到的消息数,并使用监视事件来验证这些计数以验证数据的正确性。(译者注:正确性验证其实我自己在学的时候没有想到)

加载到Hadoop集群中是通过实现一种特殊的Kafka输入格式来完成的,该格式允许MapReduce作业直接从Kafka读取数据。MapReduce作业将加载原始数据,然后对其进行分组和压缩,以在将来进行有效处理。消息偏移的无状态代理和客户端存储再次在这里发挥作用,允许MapReduce任务管理(允许任务失败并重新启动)以自然方式处理数据加载,而不会在出现以下情况时复制或丢失消息任务重新启动。仅在成功完成作业时,数据和偏移量都存储在HDFS中。

我们选择使用Avro [2]作为序列化协议,因为它高效且支持架构演变。对于每条消息,我们将其Avro模式的ID和序列化的字节存储在有效负载中。这种模式使我们可以执行合约(enforce a contract),以确保数据生产者和消费者之间的兼容性。我们使用轻量级架构注册表服务将架构ID映射到实际架构。消费者收到消息时,它将在模式注册表中进行查询以检索模式,该模式用于将字节解码为对象(由于值是不可变的,因此每个模式只需要执行一次该查询)。(译者注:即反序列化)

5.实验结果

我们进行了一项实验研究,将Kafka与Apache ActiveMQ v5.4 [1](一种流行的JMS开源实现)和RabbitMQ v2.4 [16](一种以其性能着称的消息系统)的性能进行了比较。我们使用了ActiveMQ的默认持久消息存储KahaDB。尽管此处未介绍,但我们还测试了替代的AMQ消息存储,发现其性能与KahaDB非常相似。只要有可能,我们都会尝试在所有系统中使用可比较的设置。

我们在2台Linux机器上进行了实验,每台机器具有8个2GHz内核,16GB内存,具有RAID 10的6个磁盘。这两台机器通过1Gb网络链路连接。其中一台机器用作中间人,另一台机器用作生产者或消费者。

生产者测试:我们将所有系统中的代理配置为异步将消息刷新到其持久性存储。对于每个系统,我们运行一个生产者来发布总共1000万条消息,每条消息200个字节。我们配置了Kafka生产者以1和50的大小批量发送消息。ActiveMQ和RabbitMQ似乎没有简单的方法来批量发送消息,并且我们假定它使用的批量大小为1。结果如图4所示。x轴表示随时间推移发送给代理的数据量(以MB为单位),y轴表示生产者吞吐量(以每秒消息数为单位)。平均而言,Kafka可以以每秒50,000和400,000条消息的速度发布消息,批量大小分别为1和50。这些数字比ActiveMQ高几个数量级,比RabbitMQ高至少2倍。
图4 生产者性能
图4 生产者性能

卡夫卡表现出色的原因有很多。首先,Kafka生产者当前不等待代理人的确认,而是以代理人可以处理的最快速度发送消息。这大大提高了发布者的吞吐量。批量大小为50,单个Kafka生产商几乎饱和了生产商和代理之间的1Gb链路。对于日志聚合情况,这是一种有效的优化,因为必须异步发送数据,以避免将任何等待时间引入流量的实时服务中。我们注意到,在没有确认生产者的情况下,不能保证代理实际上收到了每个已发布的消息。对于许多类型的日志数据,希望以持久性换取吞吐量,只要所丢弃消息的数量相对较小即可。但是,我们确实计划在将来解决持久性问题,以获取更多关键数据。

其次,Kafka具有更有效的存储格式。平均而言,每条消息在Kafka中的开销为9字节,而在ActiveMQ中为144字节。这意味着ActiveMQ使用的存储空间比Kafka多70%,用于存储相同的1000万条消息。 ActiveMQ的一项开销来自JMS所需的繁重消息头。另一个开销是维护各种索引结构的成本。我们观察到ActiveMQ中最繁忙的线程之一花费了大部分时间来访问B树以维护消息元数据和状态。(译者注:kafka各种操作都是接近O(1))最后,批处理通过摊销(原文是amortizing)RPC开销大大提高了吞吐量。在Kafka中,每批50条消息将吞吐量提高了近一个数量级。

消费者测试:在第二个实验中,我们测试了消费者的表现。同样,对于所有系统,我们仅使用一个消费者就可以检索总共1000万条消息。我们配置了所有系统,以便每个拉取请求应预取大约相同数量的数据-最多1000条消息或大约200KB。对于ActiveMQ和RabbitMQ,我们将消费者确认模式设置为自动。由于所有消息都适合内存,因此所有系统都在提供来自底层文件系统的页面缓存或某些内存缓冲区中的数据。结果如图5所示。
在这里插入图片描述
图5.消费者表现

平均而言,Kafka每秒消耗22,000条消息,是ActiveMQ和RabbitMQ的4倍以上。我们可以想到几个原因。首先,由于Kafka具有更有效的存储格式,因此将更少的字节从代理传输到卡夫卡的消费者。其次,ActiveMQ和RabbitMQ中的代理都必须维护每个消息的传递状态。我们观察到,在此测试期间ActiveMQ线程之一正在忙于将KahaDB页面写入磁盘。相反,Kafka代理上没有磁盘写入活动。最后,通过使用sendfile API,Kafka减少了传输开销。

在结束本节时,我们注意到该实验的目的并不是要表明其他消息传递系统不如Kafka。毕竟,ActiveMQ和RabbitMQ都具有比Kafka更多的功能。重点是说明通过专用系统可以实现的潜在性能提升。

6. 总结与展望

我们提出了一种称为Kafka的新型系统,用于处理大量日志数据流。像消息传递系统一样,Kafka使用基于拉的使用模型,该模型允许应用程序以自己的速率使用数据并在需要时重复消费。通过专注于日志处理应用程序,Kafka实现了比常规消息传递系统更高的吞吐量。它还提供集成的分布式支持,并且可以扩展。我们已经在LinkedIn上成功地将Kafka用于离线和在线应用程序。

我们将来会遵循许多方向。首先,我们计划在多个代理之间添加消息的内置复制,即使在无法恢复的机器故障的情况下,也可以保证持久性和数据可用性。我们希望同时支持异步和同步复制模型,以在生产者延迟和提供的保证强度之间进行权衡。应用程序可以根据其对耐用性,可用性和吞吐量的要求来选择合适的冗余级别。(译者注:其实这里就是CAP理论的权衡)其次,我们要在Kafka中添加一些流处理功能。从Kafka取回消息后,实时应用程序通常会执行类似的操作,例如基于窗口的计数以及将每个消息与辅助存储中的记录或另一个流中的消息连接在一起。(译者注:kafka stream做的事情,其实flink更加完善的支持了)在最低级别上,这可以通过在发布过程中对联接密钥上的消息进行语义分区来支持,以便使用特定键发送的所有消息都到达相同的分区(译者注:其实就是一致性哈希),从而被同一个消费者处理。这为处理消费类计算机群集中的分布式流提供了基础。最重要的是,我们认为有用的流实用程序库(例如不同的窗口功能或联接技术)将对此类应用程序有益。

参考文献
[1] http://activemq.apache.org/
[2] http://avro.apache.org/
[3] Cloudera’s Flume, https://github.com/cloudera/flume
[4] http://developer.yahoo.com/blogs/hadoop/posts/2010/06/ena bling_hadoop_batch_processi_1/
[5] Efficient data transfer through zero copy: https://www.ibm.com/developerworks/linux/library/j-zerocopy/
[6] Facebook’s Scribe, http://www.facebook.com/note.php?note_id=32008268919
[7] IBM Websphere MQ: http://www-01.ibm.com/software/integration/wmq/
[8] http://hadoop.apache.org/
[9] http://hadoop.apache.org/hdfs/
[10] http://hadoop.apache.org/zookeeper/
[11] http://www.slideshare.net/cloudera/hw09-hadoop-based-data-mining-platform-for-the-telecom-industry
[12] http://www.slideshare.net/prasadc/hive-percona-2009
[13] https://issues.apache.org/jira/browse/ZOOKEEPER-775
[14] JAVA Message Service: http://download.oracle.com/javaee/1.3/jms/tutorial/1_3_1-fcs/doc/jms_tutorialTOC.html.
[15] Oracle Enterprise Messaging Service: http://www.oracle.com/technetwork/middleware/ias/index-093455.html
[16] http://www.rabbitmq.com/
[17] TIBCO Enterprise Message Service: http://www.tibco.com/products/soa/messaging/
[18] Kafka, http://sna-projects.com/kafka/

点个赞吧~

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值