作为一名有着大量微服务系统处理经验的软件架构师,我经常遇到一个不断重复的问题:“我应该使用 RabbitMQ 还是 Kafka?”出于某种原因,许多开发人员认为这些技术是可以互换的。虽然在某些情况下确实如此,但 RabbitMQ 还是 Kafka 之间存在根本上的差异。
因此,不同的场景需要不同的解决方案,选择错误的方案会严重影响我们的软件开发设计以及后续维护软件。
本文的目标首先是介绍基本的异步消息传递模式。然后继续介绍 RabbitMQ 和 Kafka 及其内部结构。第 2 部分重点介绍了这些平台之间的关键区别、它们的各种优点和缺点,以及如何在两者之间进行选择。
异步消息传递模式
异步消息传递是一种消息传递方案,其中生产者的消息生成与消费者的消息处理分离。在消息传递系统中,我们通常会分为两种主要的消息传递模式:队列模式和发布/订阅模式。
队列模式
在队列模式中,队列暂时将生产者与消费者解耦。多个生产者可以向同一个队列发送消息。然后当消费者处理消息时,消息会被锁定然后从队列中删除,并且不再可用。
队列模式通常就是一个消息只能被一个消费者处理。
消息队列
附带说明一下,如果消费者无法处理某个消息,消息平台通常会将消息返回到队列,以供其他消费者使用。除了解耦之外,队列还允许我们扩展生产者和消费者,并针对错误处理提供容错能力。
发布/订阅模式
在发布/订阅模式中,单个消息可以由多个订阅者同时接收和处理。
发布/订阅
例如,此模式允许发布者通知所有订阅者系统中发生了某些情况。在 RabbitMQ 中,主题是一种特定类型的 pub/sub 实现(确切地说是一种交换类型),但在本文中,我将主题称为整个 pub/sub 的表示。
一般来说,订阅有两种类型:
- 临时订阅,其中订阅仅在使用者启动并运行时才有效。一旦消费者关闭,他们的订阅和尚未处理的消息就会丢失。
- 持久订阅,只要未显式删除,订阅就会得到维护。当消费者关闭时,消息平台会维持订阅,稍后可以恢复消息处理。
RabbitMQ
RabbitMQ 是消息代理的一种实现 — 通常称为服务总线。它本身支持上述两种消息传递模式。消息代理的其他流行实现包括 ActiveMQ、ZeroMQ、Azure 服务总线和 Amazon Simple Queue Service (SQS)。所有这些实现都有很多共同点,本文中描述的许多概念适用于其中的大多数。
Queues
RabbitMQ 支持开箱即用的经典消息队列。开发人员定义命名队列,然后发布者可以将消息发送到该命名队列。反过来,消费者使用相同的队列来检索消息来处理它们。
Message exchanges
RabbitMQ 通过使用消息交换机来实现 pub/sub。发布者将其消息发布到消息交换机,不用知道这些消息的订阅者是谁。
每个订阅交换机的消费者都会创建一个队列,然后消息交换机将生成的消息排队以供消费者使用。它还可以根据各种路由规则过滤某些订阅者的消息。
RabbitMQ message exchange
值得注意的是,RabbitMQ 支持临时订阅和持久订阅。消费者可以通过 RabbitMQ 的 API 决定他们想要使用的订阅类型。
由于 RabbitMQ 的架构,我们还可以创建一种混合方法,其中一些订阅者形成消费者组,这些消费者组以特定队列上竞争消费者的形式共同处理消息。通过这种方式,我们实现了发布/订阅模式,同时还允许一些订阅者扩展以处理接收到的消息。
发布/订阅和队列相结合
Apache Kafka
Apache Kafka 是一个分布式流处理平台。
与基于队列和交换的 RabbitMQ 不同,Kafka 的存储层是使用分区事务日志实现的。Kafka 还提供了 Streams API 来实时处理流,以及 Connectors API 来轻松与各种数据源集成。不过,这些超出了本文的范围。
云服务商为 Kafka 的存储层提供了替代解决方案。这些解决方案包括 Azure 事件中心,在某种程度上还包括 AWS Kinesis Data Streams。Kafka 的流处理功能还有特定于云的开源替代方案,同样,这些也超出了本文的范围。
Topics
Kafka 没有实现队列的概念。Kafka 将记录集合存储在称为主题的类别中。
对于每个主题,Kafka 都会维护一个分区的消息日志。每个分区都是一个有序的、不可变的记录序列,其中不断附加消息。
Kafka 在消息到达时将其附加到这些分区。默认情况下,它使用循环分区器在分区之间均匀地传播消息。
生产者可以修改此行为以创建逻辑消息流。例如在多租户应用程序中,我们可能希望根据每条消息的租户 ID 创建逻辑消息流。在物联网场景中,我们可能希望将每个生产者的身份不断映射到特定分区。确保来自同一逻辑流的所有消息映射到同一分区,以保证它们按顺序传递给消费者。
Kafka producers
消费者通过维护这些分区的偏移量(或索引)并按顺序读取它们来消费消息。
单个消费者可以使用多个主题,并且消费者可以扩展,直至与可用分区数量一致。
因此,在创建主题时,应仔细考虑该主题的消息传递的预期吞吐量。共同消费某个主题的一组消费者称为消费者组。Kafka 的 API 通常负责消费者组中消费者之间分区处理的平衡以及消费者当前分区偏移量的存储。
Kafka consumers
使用 Kafka 实现消息传递
Kafka 的内部实现其实很好地反映了 pub/sub 模式。
生产者可以向特定主题发送消息,多个消费者组可以消费同一条消息。每个消费者组都可以单独扩展以处理负载。由于消费者维护其分区偏移量,因此他们可以选择持久订阅(在重新启动时维持其偏移量)或临时订阅(即丢弃偏移量并在每次启动时从每个分区中的最新记录重新启动)。
Kafka 其实是不太适合队列模式的消息传递。当然我们可以创建一个只有一个消费者组的主题来模拟经典的消息队列。但这有多个缺点,在本文第 2 部分我们将详细讨论。
第 2 部分文章地址:https://betterprogramming.pub/rabbitmq-vs-kafka-1779b5b70c41
值得注意的是,无论消费者是否消费了这些消息,Kafka 都会将消息保留在分区中直至预先配置的时间段内。这种保留意味着消费者可以自由地重读过去的消息。此外,开发人员还可以使用 Kafka 的存储层来实现事件溯源和审计日志等机制。
结束语
虽然 RabbitMQ 和 Kafka 有时可以互换,但它们的实现却截然不同。因此,我们不能将它们视为同一类别工具的成员。一个是消息代理,另一个是分布式流平台。
作为解决方案架构师,我们应该认识到这些差异,并积极考虑针对给定场景应使用哪些类型的解决方案。