在本文中,我们将深入探讨这两个消息平台之间的显著差异,以及它们在不同使用场景下的优势和劣势。
首先,让我们来了解一下 RabbitMQ 和 Kafka 的显著区别。RabbitMQ 是一个消息代理中间件,而 Apache Kafka 是一个分布式流处理平台。虽然这两者在表面上都与消息传递有关,但它们的用途和设计理念却有着根本性的不同。
发生这种缺乏排序保证的情况是因为消费者可能会在读取消息后将消息返回(或重新传递)到队列(例如在处理失败的情况下)。一旦消息返回,另一个消费者就可以拿起它进行处理,即使它已经消费了后面的消息。因此多个消费者之间无法有序处理消息。
我们可以通过将消费者并发数限制为 1 来重新保证 RabbitMQ 中的消息顺序。更准确地说,单个消费者内的线程计数要限制为 1,因为任何并行的消息处理都可能导致消息乱序问题。如果我们将自己限制为一个单线程消费者虽然能保证消息顺序,但这会严重影响我们系统扩展消息的处理能力,因此我们不应该轻易的这样做。另一方面,Kafka 为消费者在消息处理时提供了可靠的排序保证。Kafka 保证发送到同一主题分区的所有消息都按顺序处理。
来自同一数据流的所有消息都会被放置在同一分区中,从而使消费者组按顺序处理它们。我们应该注意到,在消费者组中,每个分区都是由单个消费者的单个线程处理的。因此我们无法扩展单个分区的处理。不过在 Kafka 中,我们可以扩展主题内的分区数量,从而使每个分区接收更少的消息,并为额外的分区添加额外的消费者。
Kafka 是明显的赢家,因为它允许消息按顺序处理。RabbitMQ 在这方面只有较弱的保证。
Kafka 不允许消费者在轮询主题之前过滤主题中的消息。订阅的消费者无一例外地接收分区中的所有消息。作为开发人员,你可以使用 Kafka 用于流作业,该作业从主题读取消息,过滤它们,然后将它们推送到消费者订阅的另一个主题。尽管也可以实现,但相比与 RabbitMQ 需要更多的努力和维护,并且需要更多的活动部件。
RabbitMQ 在路由和过滤消息供消费者使用时提供卓越的支持。
RabbitMQ 提供了多种消息计时控制功能,如消息生存时间(TTL)和延迟/定时消息。TTL 允许消息设置有效期,而延迟/定时消息允许生产者延迟消息的路由到消费者队列的时间。Kafka 不提供这些功能,消息一旦到达就会立即被写入分区,并且不支持消息的定时或延迟路由。此外 Kafka 没有为消息提供 TTL 机制,尽管我们可以在应用程序级别实现一种机制。
我们还必须记住,Kafka 分区是一个仅追加的事务日志。因此它无法操纵消息时间(或分区内的位置)。
TTL 对于时间敏感但经过一段时间而没有处理后就变得无关紧要的命令特别有用。
RabbitMQ 通过使用插件支持延迟/预定消息。当在消息交换上启用此插件时,生产者可以向 RabbitMQ 发送消息,并且生产者可以延迟 RabbitMQ 将此消息路由到消费者队列的时间。
此功能允许开发人员安排未来的命令,这些命令在此之前不应该被处理。例如当生产者遇到限制规则时,我们可能希望将特定命令的执行延迟到稍后的时间。
RabbitMQ 毫无疑问地赢得了这一项目的胜利。
在消息保留方面,Kafka 与 RabbitMQ 也有不同的处理方式。Kafka 会根据主题的配置保留所有消息至超时时间,无论消费者是否已成功处理。而 RabbitMQ 会在消费者成功处理消息后将其删除,无法修改这一行为。
Kafka 的性能不依赖于存储大小。因此从理论上讲,人们几乎可以无限期地存储消息,而不会影响性能(只要你的节点足够大来存储这些分区)。
Kafka 设计上就旨在消息保留,而 RabbitMQ 则不然。这里不需要竞争,Kafka 被宣布为获胜者。
在消息处理过程中,可能会发生两种类型的错误:瞬时故障和持续性故障。瞬时故障通常是由临时问题引起的,如网络连接问题或服务崩溃,可以通过重试来解决。持续性故障则是由无法通过重试解决的永久性问题引起的,需要更复杂的故障处理逻辑。
RabbitMQ 提供了传递重试和死信交换(DLX)等工具来处理消息处理失败。DLX 可以自动将失败的消息路由到另一个交换机,并应用进一步的处理规则,包括延迟重试和重试计数。
这里要记住的最重要的事情是,在 RabbitMQ 中,当消费者忙于处理和重试特定消息时(甚至在将其返回到队列之前),其他消费者可以并发处理该消息之后的消息。
当特定消费者重试特定消息时,整个消息处理不会被卡住。因此消息使用者可以根据需要同步重试消息,而不会影响整个系统。消费者1可以继续重试消息1,而其他消费者则继续处理消息。
与 RabbitMQ 相反,Kafka 不提供任何开箱即用的此类工具。对于 Kafka 我们需要在应用程序中提供和实现消息重试机制。另外我们应该注意,当消费者忙于同步重试特定消息时,无法处理来自同一分区的其他消息。我们无法拒绝并重试特定消息并提交该消息之后的消息,因为消费者无法更改消息顺序。正如你所记得的,分区只是一个仅追加日志。
有一种类型的解决方案是应用程序可以将失败的消息提交到“重试主题”并从那里处理重试,不过这样我们就会失去了消息的顺序性。Uber 工程部提供了解决此类问题的示例,可以在 Uber.com 上找到。如果消息处理的延迟不是问题,那么使用普通 Kafka 可能就足够了。如果消费者在重试消息时遇到困难,则不会处理底部分区中的消息。
RabbitMQ 是该项目上的赢家,因为它提供了一种开箱即用的解决此问题的工具。
从性能角度来看,通常认为 Kafka 比 RabbitMQ 具有更好的性能。Kafka 使用顺序磁盘 I/O 来提高性能,且通过分区架构可以更好地水平扩展。大型 Kafka 集群通常能够处理数十万甚至数百万条消息每秒的负载。RabbitMQ 的性能较低,典型的集群部署只能处理数万条消息每秒的负载。尽管某些情况下 RabbitMQ 也能够达到较高的性能,但需要更多的节点和复杂的配置。
虽然通用基准测试对特定情况的适用性有限,但 Kafka 通常被认为比 RabbitMQ 具有更好的性能。Kafka 使用顺序磁盘 I/O 来提高性能。它使用分区的架构意味着它的水平扩展(横向扩展)比 RabbitMQ 更好,而 RabbitMQ 的垂直扩展(纵向扩展)更好。大型 Kafka 的集群部署通常每秒可以处理数十万条消息,甚至每秒处理数百万条消息。
典型的 RabbitMQ 部署包括三到七个节点集群,这些节点集群不一定能最佳地分配队列之间的负载。这些典型的集群通常只能每秒处理数万条消息的负载。
然而值得注意的是,大多数系统都不会达到每秒上万的消息处理量!因此除非你正在构建下一个拥有数百万用户的热门软件系统,否则你不需要太关心扩展性,因为这两个平台都可以很好地为你服务。
Kafka 则使用愚蠢代理人和聪明消费者的模型。消费者需要协调分区的约定,管理和存储分区的偏移索引,这使得消费者端需要更多的资源和复杂性。
通常情况下,你可能会在一个系统中同时使用这两个平台,根据需求选择合适的工具。例如,在事件驱动架构的系统中,你可以使用 RabbitMQ 用于命令传递,再使用 Kafka 用于业务事件通知。
最终,作为架构师,你需要权衡系统的需求、现有技术栈、开发人员的熟悉度以及运营成本等因素,选择最适合你的消息平台。不同的场景可能需要不同的工具,因此要根据具体情况做出明智的选择。