消息大作战:RabbitMQ vs. Kafka|选择了哪个等于选择了一种生活方式

介绍

作为一位有着丰富微服务系统处理经验的软件架构师,你可能常常纠结于一个问题:"我应该使用 RabbitMQ 还是 Kafka?"虽然许多开发人员可能认为这两种技术是可以互换的,但事实上,RabbitMQ 和 Kafka 之间存在根本性的差异,而选择错误的方案可能会严重影响系统的开发和维护。

在本文中,我们将深入探讨这两个消息平台之间的显著差异,以及它们在不同使用场景下的优势和劣势。

RabbitMQ 和 Kafka 的显著区别

首先,让我们来了解一下 RabbitMQ 和 Kafka 的显著区别。RabbitMQ 是一个消息代理中间件,而 Apache Kafka 是一个分布式流处理平台。虽然这两者在表面上都与消息传递有关,但它们的用途和设计理念却有着根本性的不同。

消息顺序

一项重要的区别是消息的顺序性。在 RabbitMQ 中,对于发送到队列或交换器的消息,它提供了有限的顺序性保证。虽然单个消费者可以按照生产者发送消息的顺序处理消息,但一旦多个消费者从同一个队列读取消息,就无法保证消息的处理顺序。这是因为多个消费者可能会交错地处理消息,导致乱序。

RabbitMQ 文档声明了以下有关其消息顺序的内容:

"在一个通道中发布的消息,经过一个交换机、一个队列和一个传出通道后,将按照发送的顺序被接收。" --- RabbitMQ Broker Semantics

换句话说,当我们只有一个消息消费者,它就会按顺序接收消息。然而一旦我们有多个消费者从同一个队列读取消息,我们就无法保证消息的处理顺序。

发生这种缺乏排序保证的情况是因为消费者可能会在读取消息后将消息返回(或重新传递)到队列(例如在处理失败的情况下)。

一旦消息返回,另一个消费者就可以拿起它进行处理,即使它已经消费了后面的消息。因此多个消费者之间无法有序处理消息

我们可以通过将消费者并发数限制为 1 来重新保证 RabbitMQ 中的消息顺序。更准确地说,单个消费者内的线程计数要限制为 1,因为任何并行的消息处理都可能导致消息乱序问题。

如果我们将自己限制为一个单线程消费者虽然能保证消息顺序,但这会严重影响我们系统扩展消息的处理能力,因此我们不应该轻易的这样做。

另一方面,Kafka 为消费者在消息处理时提供了可靠的排序保证。Kafka 保证发送到同一主题分区的所有消息都按顺序处理。

相比之下,Kafka 提供了可靠的消息排序保证。Kafka 会确保发送到同一主题分区的所有消息都按顺序处理,这对于需要严格的消息排序非常重要。

默认情况下,Kafka 使用循环分区程序将消息放置在分区中。但是生产者可以在每个消息上设置分区键,以创建逻辑数据流(例如来自同一设备的消息,或属于同一租户的消息)。

来自同一数据流的所有消息都会被放置在同一分区中,从而使消费者组按顺序处理它们。

我们应该注意到,在消费者组中,每个分区都是由单个消费者的单个线程处理的。因此我们无法扩展单个分区的处理。

不过在 Kafka 中,我们可以扩展主题内的分区数量,从而使每个分区接收更少的消息,并为额外的分区添加额外的消费者。

赢家

Kafka 是明显的赢家,因为它允许消息按顺序处理。RabbitMQ 在这方面只有较弱的保证。

消息路由

另一个区别是消息的路由和过滤。RabbitMQ 允许消费者定义路由规则,将消息路由到消息交换机的订阅者。不同类型的交换机,如主题交换(topic exchange)和标头交换(headers exchange),允许消费者根据消息的特定属性来过滤消息,提供了灵活的消息路由功能。

与此不同,Kafka 不允许消费者在轮询主题时过滤消息,消费者组中的每个消费者都会接收分区中的所有消息。这意味着在 Kafka 中,过滤和路由消息需要在应用程序层面处理,相对更复杂。

Kafka 不允许消费者在轮询主题之前过滤主题中的消息。订阅的消费者无一例外地接收分区中的所有消息。

作为开发人员,你可以使用 Kafka 用于流作业,该作业从主题读取消息,过滤它们,然后将它们推送到消费者订阅的另一个主题。尽管也可以实现,但相比与 RabbitMQ 需要更多的努力和维护,并且需要更多的活动部件。

赢家

RabbitMQ 在路由和过滤消息供消费者使用时提供卓越的支持。

消息计时

RabbitMQ 提供了多种消息计时控制功能,如消息生存时间(TTL)和延迟/定时消息。TTL 允许消息设置有效期,而延迟/定时消息允许生产者延迟消息的路由到消费者队列的时间。Kafka 不提供这些功能,消息一旦到达就会立即被写入分区,并且不支持消息的定时或延迟路由。此外 Kafka 没有为消息提供 TTL 机制,尽管我们可以在应用程序级别实现一种机制。

我们还必须记住,Kafka 分区是一个仅追加的事务日志。因此它无法操纵消息时间(或分区内的位置)。

消息生存时间 (TTL)

TTL 属性可以与发送到 RabbitMQ 的每条消息相关联。设置 TTL 可以由发布者直接完成,也可以作为队列本身的策略来完成。

指定 TTL 允许系统限制消息的有效期。如果消费者没有及时处理它,那么它会自动从队列中删除(并转移到死信交换,稍后会详细介绍)。

TTL 对于时间敏感但经过一段时间而没有处理后就变得无关紧要的命令特别有用。

延迟/定时消息

RabbitMQ 通过使用插件支持延迟/预定消息。当在消息交换上启用此插件时,生产者可以向 RabbitMQ 发送消息,并且生产者可以延迟 RabbitMQ 将此消息路由到消费者队列的时间。

此功能允许开发人员安排未来的命令,这些命令在此之前不应该被处理。例如当生产者遇到限制规则时,我们可能希望将特定命令的执行延迟到稍后的时间。

赢家

RabbitMQ 毫无疑问地赢得了这一项目的胜利。

消息保留

在消息保留方面,Kafka 与 RabbitMQ 也有不同的处理方式。Kafka 会根据主题的配置保留所有消息至超时时间,无论消费者是否已成功处理。而 RabbitMQ 会在消费者成功处理消息后将其删除,无法修改这一行为。

相比之下,Kafka 根据设计将所有消息保留至每个主题配置的超时时间。在消息保留方面,Kafka 不关心消费者的消费状态,因为它充当消息日志。

消费者可以根据需要消费每条消息,并且可以通过操纵分区偏移量"及时"来回移动。Kafka 会定期检查主题中消息的年龄,并驱逐那些足够老的消息。

Kafka 的性能不依赖于存储大小。因此从理论上讲,人们几乎可以无限期地存储消息,而不会影响性能(只要你的节点足够大来存储这些分区)。

赢家

Kafka 设计上就旨在消息保留,而 RabbitMQ 则不然。这里不需要竞争,Kafka 被宣布为获胜者。

故障处理

在消息处理过程中,可能会发生两种类型的错误:瞬时故障和持续性故障。瞬时故障通常是由临时问题引起的,如网络连接问题或服务崩溃,可以通过重试来解决。持续性故障则是由无法通过重试解决的永久性问题引起的,需要更复杂的故障处理逻辑。

RabbitMQ 提供了传递重试和死信交换(DLX)等工具来处理消息处理失败。DLX 可以自动将失败的消息路由到另一个交换机,并应用进一步的处理规则,包括延迟重试和重试计数。

Kafka 不提供这些工具,故障处理需要在应用程序层面实现,较为复杂。

下文提供了有关在 RabbitMQ 中处理重试的可能模式的更多见解。

engineering.nanit.com/rabbitmq-re...

这里要记住的最重要的事情是,在 RabbitMQ 中,当消费者忙于处理和重试特定消息时(甚至在将其返回到队列之前),其他消费者可以并发处理该消息之后的消息。

当特定消费者重试特定消息时,整个消息处理不会被卡住。因此消息使用者可以根据需要同步重试消息,而不会影响整个系统。

消费者1可以继续重试消息1,而其他消费者则继续处理消息

与 RabbitMQ 相反,Kafka 不提供任何开箱即用的此类工具。对于 Kafka 我们需要在应用程序中提供和实现消息重试机制。

另外我们应该注意,当消费者忙于同步重试特定消息时,无法处理来自同一分区的其他消息。

我们无法拒绝并重试特定消息并提交该消息之后的消息,因为消费者无法更改消息顺序。正如你所记得的,分区只是一个仅追加日志。

有一种类型的解决方案是应用程序可以将失败的消息提交到"重试主题"并从那里处理重试,不过这样我们就会失去了消息的顺序性。

Uber 工程部提供了解决此类问题的示例,可以在 Uber.com 上找到。如果消息处理的延迟不是问题,那么使用普通 Kafka 可能就足够了。

Uber.com 地址:eng.uber.com/reliable-re...

如果消费者在重试消息时遇到困难,则不会处理底部分区中的消息

赢家

RabbitMQ 是该项目上的赢家,因为它提供了一种开箱即用的解决此问题的工具。

扩展性

从性能角度来看,通常认为 Kafka 比 RabbitMQ 具有更好的性能。Kafka 使用顺序磁盘 I/O 来提高性能,且通过分区架构可以更好地水平扩展。大型 Kafka 集群通常能够处理数十万甚至数百万条消息每秒的负载。

RabbitMQ 的性能较低,典型的集群部署只能处理数万条消息每秒的负载。尽管某些情况下 RabbitMQ 也能够达到较高的性能,但需要更多的节点和复杂的配置。

虽然通用基准测试对特定情况的适用性有限,但 Kafka 通常被认为比 RabbitMQ 具有更好的性能。Kafka 使用顺序磁盘 I/O 来提高性能。

它使用分区的架构意味着它的水平扩展(横向扩展)比 RabbitMQ 更好,而 RabbitMQ 的垂直扩展(纵向扩展)更好。

大型 Kafka 的集群部署通常每秒可以处理数十万条消息,甚至每秒处理数百万条消息。

过去的时间里,Pivotal 团队发布了 RabbitMQ 集群可以每秒处理 100 万条消息的文章如下。然而它是在 30 个节点的集群上实现的,负载以最佳方式分布在多个队列和交换器上。

RabbitMQ Hits One Million Messages Per Second on Google Compute Engine,地址:tanzu.vmware.com/content/blo...

典型的 RabbitMQ 部署包括三到七个节点集群,这些节点集群不一定能最佳地分配队列之间的负载。这些典型的集群通常只能每秒处理数万条消息的负载。

赢家

虽然这两个平台都可以处理大量负载,但 Kafka 通常比 RabbitMQ 具有更好的扩展性,并且可以实现更高的吞吐量,从而赢得了这一轮。

然而值得注意的是,大多数系统都不会达到每秒上万的消息处理量!因此除非你正在构建下一个拥有数百万用户的热门软件系统,否则你不需要太关心扩展性,因为这两个平台都可以很好地为你服务。

消费者复杂性

RabbitMQ 使用智能代理人和愚蠢消费者的模型。消费者注册到队列上,RabbitMQ 会主动推送消息给它们,无需担心消息的管理和删除。

Kafka 则使用愚蠢代理人和聪明消费者的模型。消费者需要协调分区的约定,管理和存储分区的偏移索引,这使得消费者端需要更多的资源和复杂性。

如何选择?

那么,什么时候应该选择 RabbitMQ,什么时候应该选择 Kafka 呢?

RabbitMQ 适用于:

  1. 需要灵活的消息路由和过滤功能,或者消费者需要根据消息属性进行选择性消费。
  2. 需要控制消息的计时,包括消息的过期和延迟路由。
  3. 需要高级故障处理功能,以处理消费者无法处理的消息。
  4. 希望更简单的消费者实现。

Kafka 适用于:

  1. 需要严格的消息排序的场景。
  2. 需要保留消息较长时间,包括重放过去的消息。
  3. 当系统需要水平扩展以处理大规模负载时。

通常情况下,你可能会在一个系统中同时使用这两个平台,根据需求选择合适的工具。例如,在事件驱动架构的系统中,你可以使用 RabbitMQ 用于命令传递,再使用 Kafka 用于业务事件通知。

最终,作为架构师,你需要权衡系统的需求、现有技术栈、开发人员的熟悉度以及运营成本等因素,选择最适合你的消息平台。不同的场景可能需要不同的工具,因此要根据具体情况做出明智的选择。

最后

这篇文章的初衷是,许多开发人员认为 RabbitMQ 和 Kafka 是可以互换的。我希望大家看到这文章后,能更加深入了解这些消息平台的内部构造以及它们之间的技术差异。这些差异反过来又会影响这些消息平台所服务的系统。RabbitMQ 和 Kafka 都很棒,可以适用于多种系统。

相关推荐
bubble小拾2 小时前
Kafka日志索引详解与常见问题分析
分布式·kafka
杰信步迈入C++之路3 小时前
【RabbitMQ】RabbitMQ 概述
分布式·rabbitmq
BLUcoding4 小时前
RabbitMQ08_保证消息可靠性
java·rabbitmq
月夜星辉雪6 小时前
【RabbitMQ 项目】服务端:数据管理模块之消息管理
分布式·rabbitmq
happycao12312 小时前
kafka 生产者拦截器
中间件·kafka
XMYX-012 小时前
Kafka 命令详解及使用示例
kafka
coisini.cn12 小时前
Windows10、CentOS Stream9 环境下安装kafka_2.12-3.6.2记录
运维·zookeeper·kafka·windows10·centos stream 9
不能再留遗憾了12 小时前
RabbitMQ 高级特性——发送方确认
分布式·rabbitmq·ruby
益达_z12 小时前
中间件知识点-消息中间件(Rabbitmq)一
分布式·中间件·rabbitmq
.生产的驴15 小时前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq