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

摘要

日志处理已成为消费者互联网公司数据管道中的重要组成部分。我们介绍Kafka,这是一个分布式消息系统,我们开发该系统是为了以低延迟收集和发送大量日志数据。我们的系统结合了现有日志聚合器和消息传递系统的思想,同时适用于离线和在线消息消费使用。我们在Kafka中做出了许多非常规但实用的设计选择,以使我们的系统高效且可扩展。我们的实验结果表明,与两种流行的消息传递系统相比,Kafka具有卓越的性能。我们已经在生产中使用Kafka有一段时间了,它每天要处理数百GB的新数据。

常用术语

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

关键词

消息传递,分布式,日志处理,吞吐量,在线。

1. 简介

在任何规模较大的互联网公司中都会生成大量“日志”数据。这类数据通常包括(1) 与登录、浏览量、点击、“喜欢”、分享、评论和搜索查询相对应的用户活动事件;(2) 操作指标,例如服务调用堆栈、调用延迟、错误,以及系统指标,例如每台计算机上的CPU、内存、网络或磁盘利用率。日志数据一直是用于跟踪用户参与度、系统利用率和其他指标的分析的组成部分。但是,网络应用的最新趋势已使生产数据管道的一部分的活动数据被直接用于站点功能。这些用途包括(1) 搜索相关性,(2) 可能由活动流中的项目受欢迎程度或并发所驱动的推荐,(3) 广告定位和报表,(4) 防止滥用行为(例如垃圾邮件或未经授权的数据抓取)的安全应用,以及(5) 新闻摘要功能,这些功能汇总了用户状态更新或以供其“朋友”或“连接”读取的操作。

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

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

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

2. 相关工作

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

在过去的几年中,已经建立了许多专业的日志聚合器。Facebook使用名为Scribe的系统。每个前端计算机都可以通过套接字将日志数据发送到一组Scribe机器。每台Scribe机器都会汇总日志条目,并定期将它们转储到HDFS[9]或NFS设备。雅虎的数据高速公路项目具有类似的数据流。一组计算机聚集来自客户端的事件并推出“分钟”文件,然后将其添加到HDFS。Flume是由Cloudera开发的相对较新的日志聚合器。它支持可扩展的“管道”和“接收器”,并使流式日志数据非常灵活。它还具有更多集成的分布式支持。但是,大多数这些系统是为离线使用日志数据而构建的,并且经常不必要地向消费者暴露实现细节(例如“分钟文件”)。此外,它们中的大多数使用“推送”模式,其代理将数据转发给消费者。在LinkedIn,我们发现“拉取”模式更适合我们的应用,因为每个消费者都可以以其可以维持的最大速率检索消息,并避免被以超出其处理能力的推送速度的消息淹没。拉取模式还可以轻松地回退消费者,我们将在第3.2节的末尾讨论这种好处。

最近,Yahoo!Research开发了一种新的分布式发布/订阅系统,称为HedWig[13]。HedWig具有高度的可扩展性和可用性,并提供强大的持久性保证。但是,它主要用于保存数据存储的提交日志。

3. Kafka架构和设计原则

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

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

生产者代码示例:

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集群通常由多个代理组成。为了平衡负载,一个主题分为多个分区,每个代理存储一个或多个这些分区。多个生产者和消费者可以同时发布和拉取消息。在3.1节中,我们描述了代理上单个分区的布局以及我们选择的一些设计选择,以使访问分区更加有效。在3.2节中,我们描述了生产者和消费者如何在分布式环境中与多个代理进行交互。我们将在第3.3节中讨论Kafka的交付保证。

图1. Kafka架构

3.1 单分区的效率

我们在Kafka中做出了一些选择,以提高系统效率。

3.1.1 简单的存储

Kafka的存储布局非常简单。主题的每个分区都对应一个逻辑日志。在物理上,日志是由一组大小近似相同(例如1GB)的段文件实现的。生产者每次将消息发布到分区时,代理都将消息简单地附加到最后一个段文件。为了获得更好的性能,我们仅在发布了可配置数量的消息或经过一定时间后才将分段文件刷新到磁盘。消息仅在刷新后才暴露给消费者。

与典型的消息系统不同,存储在Kafka中的消息没有明确的消息ID。相反,每个消息都通过其在日志中的逻辑偏移量来寻址。这样避免了维护将消息ID映射到实际消息位置的辅助的、查找密集型随机访问索引结构的开销。请注意,我们的消息ID正在增加,但不是连续的。要计算下一条消息的ID,我们必须将当前消息的长度添加到其ID中。从现在开始,我们将交替使用消息ID和偏移量。

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

图2. Kafka日志

3.1.2 高效的传输

我们非常关注将数据传入和传出Kafka。之前,我们已经展示了生产者可以在单个发送请求中提交一组消息。尽管最终消费者API一次迭代一条消息,但在后台,来自消费者的每个拉取请求还可以检索到一定大小(通常为数百KB)的多条消息。

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

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

3.1.3 无状态代理

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

此设计有一个重要的附带好处。消费者可以有意地回退到旧的偏移量并重新使用数据。这违反了队列的通用协议,但是事实证明这是许多消费者的基本功能。例如,当消费者中的应用程序逻辑出现错误时,错误修复后,应用程序可以重播某些消息。这对于将ETL数据加载到我们的数据仓库或Hadoop系统中特别重要。作为另一示例,所消费的数据可以仅周期性地被刷新到持久性存储(例如,全文本索引器)。如果消费者崩溃,则未刷新的数据将丢失。在这种情况下,消费者可以将未刷新消息的最小偏移量设置检查点,并在重新启动后从该偏移量中重新消费。我们注意到,在拉取模式中支持回退消费者要比推送模型容易得多。

3.2 分布式协调

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

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

我们的第一个决定是使主题内的分区成为并行度的最小单位。这意味着在任何给定时间,来自一个分区的所有消息仅由每个消费者组中的单个消费者消费。如果我们允许多个消费者同时消费一个分区,那么他们将必须协调谁消费哪些消息,这需要加锁和状态维护开销。相反,在我们的设计中,消费进程仅在消费者重新平衡负载时才需要协调(这种情况很少发生)。为了使负载真正达到平衡,我们在一个主题中需要的分区要比每组中的消费者多得多。我们可以通过对主题进行过度分区来轻松实现这一目标。

我们做出的第二个决定是没有中央的“主”节点,而是让消费者以分散的方式在彼此之间进行协调。增加主服务器会使系统复杂化,因为我们必须进一步考虑主服务器故障。为了促进协调,我们使用了高度可用的一致性服务Zookeeper[10]。Zookeeper有一个非常简单的类文件系统API。可以创建路径、设置路径的值、读取路径的值、删除路径并列出路径的子路径。它做了一些更有趣的事情:(a) 可以在路径上注册观察者,并在路径的子路径或路径的值发生更改时得到通知;(b) 可以临时创建一个路径(与持久性相反),这意味着如果创建的客户端不存在了,则该路径将由Zookeeper服务器自动删除;© Zookeeper将其数据复制到多个服务器,这使数据高度可靠且可用。

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

在Zookeeper中创建的路径对于代理注册表、消费者注册表和所有权注册表是临时的,对于偏移量注册表是持久的。如果代理失败,则该代理上的所有分区都会自动从代理注册表中删除。消费者的故障导致它丢失在消费者注册表中的条目以及在所有者注册表中拥有的所有分区。每个消费者都在代理注册表和消费者注册表上都注册了Zookeeper观察者,并且每当代理集合或消费者组发生更改时,都将收到通知。

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

Algorithm 1: rebalance process for consumer Ci in group G
For each topic T that Ci subscribes to {
  remove partitions owned by Ci from the ownership registry
  read the broker and the consumer registries from Zookeeper
  compute PT = partitions available in all brokers under topic T
  compute CT = all consumers in G that subscribe to topic T
  sort PT and CT
  let j be the index position of Ci in CT and let N = |PT|/|CT|
  assign partitions from j*N to (j+1)*N - 1 in PT to consumer Ci
  for each assigned partition p {
    set the owner of p to Ci in the ownership registry
    let Op = the offset of partition p stored in the offset registry
    invoke a thread to pull data in partition p from offset Op
  }
}

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

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

3.3 交付保证

通常,Kafka仅保证至少一次交付。恰好一次交付通常需要两阶段提交,而对于我们的应用不是必需的。在大多数情况下,一条消息恰好一次发送给每个消费者组。但是,如果消费者进程崩溃而没有完全关闭,则接管有故障的消费者拥有的那些分区的消费者进程可能会得到一些重复的消息,这些消息是在最后一次偏移量成功提交给Zookeeper之后的。如果应用关心重复,则它必须使用我们返回给使用者的偏移量或消息中的某些唯一键来添加自己的重复数据删除逻辑。与使用两阶段提交相比,这通常是一种更经济有效的方法。

Kafka保证将来自单个分区的消息按顺序传递给消费者。但是,不能保证来自不同分区的消息的顺序。

为了避免日志损坏,Kafka将每个消息的CRC存储在日志中。如果代理上存在任何I/O错误,Kafka将运行恢复过程以删除带有不一致CRC的消息。在消息级别使用CRC还可以使我们在产生或消费消息之后检查网络错误。

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

4. Kafka在LinkedIn中的使用

在本节中,我们描述了如何在LinkedIn使用Kafka。图3显示了我们部署的简化版本。我们有一个Kafka集群与运行我们的用户界面服务的每个数据中心共置一处。前端服务生成各种日志数据,并将其批量发布到本地Kafka代理。我们依靠硬件负载平衡器将发布请求平均分配给Kafka代理集。Kafka的在线消费者在同一数据中心内的服务中运行。

图3. Kafka的部署

我们还在单独的数据中心中部署了一个Kafka集群,以进行离线分析,该集群的地理位置靠近我们的Hadoop集群和其他数据仓库基础设施。该Kafka实例运行一组嵌入式消费者,以从实时数据中心的Kafka实例中拉取数据。然后,我们运行数据加载作业,以将数据从Kafka的副本集群中拉取到Hadoop和我们的数据仓库,在此我们对数据运行各种报表作业和分析过程。我们还使用此Kafka集群进行原型设计,并能够针对原始事件流运行简单脚本以进行特定查询。无需太多调整,整个管道的端到端延迟平均约为10秒,足以满足我们的要求。

目前,Kafka每天存储数百GB的数据和近10亿条消息,随着我们完成对旧系统的切换以利用Kafka的优势,我们预计这一数量将显著增长。将来会添加更多类型的消息。当操作人员启动或停止代理进行软件或硬件维护时,重新平衡过程能够自动重定向消费。

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

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

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

5. 实验结果

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

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

5.1 生产者测试

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

图4. 生产性能

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

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

5.2 消费者测试

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

图5. 消费者性能

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

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

6. 结论与未来工作

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

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

7. 参考文献

  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/enabling_hadoop_batch_processi_1/
  5. Efficient data transfer through zero copy: https://www.ibm.com/developerworks/linux/library/jzerocopy/
  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-baseddata-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/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章