消息队列和事件流是事件驱动架构的关键组成部分,但它们究竟有何异同?在什么情况下应选择它们的哪一个?
译自 Choosing Between Message Queues and Event Streams,作者 Tun Shwe 是Quix的数据副总裁,负责领导数据战略和开发者关系。他专注于帮助公司设想并执行具有流处理前沿的战略数据愿景。他之前...
实施事件驱动架构(EDA)是一项充满挑战的任务。其中之一是选择适合工作的工具。许多事件驱动工具乍看之下似乎相似,你可能期望它们可以同样适用于相同的目的。但通常情况下并非如此,选择最适合你需求的解决方案可能会比较棘手。
以消息代理技术为例。起初选择消息代理可能似乎很简单。然而,“消息代理”是一个经常用于描述不同类型组件的总称,如事件总线、发布/订阅消息服务、消息队列系统和事件流平台。
虽然在所有这些组件的能力和用例方面存在一些重叠,但也有很多显著的区别。在拥抱其中一种消息代理技术之前(或它们的混合使用),了解这些差异是至关重要的。
我将重点关注消息队列和事件流,突显它们之间的差异、共同点以及对各种用例的适用性。作为后续阅读,我推荐阅读“事件驱动、事件流堆栈指南”,该指南涵盖了EDA的所有组件,并通过一个参考用例和决策树引导你了解每个组件的适用场景。
在讨论消息队列和事件流之前,让我们首先澄清一下“消息”和“事件”是什么意思。消息是一个通用术语,用于描述从一个组件发送到另一个组件的数据包。有不同类型的消息,包括:
- 命令消息。它携带接收者执行特定操作的指令。
- 查询消息。用于从组件获取信息的请求。
- 回复消息。由服务器或接收方回复请求/查询消息。
- 事务消息。在消息是事务的一部分并且必须以可靠的、通常是原子的方式进行处理的系统中使用。
以下是一个基本示例,演示了一条命令消息,指示银行系统启动资金转移:
{
"commandId": "cmd-987654321",
"commandType": "ProcessBankTransfer",
"data": {
"transactionId": "abc123",
"fromAccountId": "45678",
"toAccountId": "98765",
"amount": 100.00,
"currency": "USD"
}
}
与此同时,事件是系统内的重大事件或状态变化。UI中的按钮被点击、运动传感器记录运动或成功处理付款 —— 这些都是事件的示例。当事件在系统的组件之间“传播”时,它以消息的形式进行,因此事件是消息的一种类型。
以下是一个事件消息的示例,记录了上述命令消息已被处理,并且资金已成功在账户之间转移。
{
"eventId": "evt-123456789",
"eventType": "BankTransfer",
"timestamp": "2023-12-13T09:45:30Z",
"eventData": {
"transactionId": "abc123",
"fromAccountId": "45678",
"toAccountId": "98765",
"amount": 100.00,
"currency": "USD"
}
}
由于事件被打包成消息,讨论事件驱动架构时,你经常会听到“消息”和“事件”这两个术语被交替使用。然而,值得注意的是,虽然事件是消息,但并非所有消息都是事件。
现在,让我们转向消息队列和事件流。消息队列的操作原则是为即将由消费者处理的消息提供临时存储。生产者将消息发送到消息代理,后者将其存储在队列中。消费者从队列中检索消息,通常按照先进先出(FIFO)的顺序。一旦从队列中消费(并得到确认),消息就会被删除。这种设置使组件解耦,确保消息由消费者可靠有序地处理。
消息队列概述
与消息队列类似,事件流围绕生产者、消费者、消息代理和消息展开。然而,与消息队列相比,存在一些显著的差异:
- 事件流涉及连续的事件消息流动。(通常情况下,使用消息队列不会涉及如此高的数据量和速率)。
- 代理通常将事件消息存储在主题(或通道)中。与点对点队列不同,其中单个接收者消费每条消息,主题使用发布/订阅模型,允许多个消费者读取相同的消息。
- 消息可以按顺序存储更长的时间。(它们不会在被消费后立即丢弃)。
- 消息队列的主要目的是可靠地将消息从A点传递到B点,而事件流遵循不同的范例。事件流确实也能实现这一点,但除了分发之外,通常还会在将事件数据传递到目标之前实时转换它(因此,高级流程是A > 数据转换 > B)。数据转换通常涉及使用流处理技术,如Kafka Streams或Apache Flink。
事件流概述
允许实施事件流的技术与用于消息队列的技术之间存在许多区别。为了突显这些区别,我将比较Apache Kafka(事件流平台)和RabbitMQ(提供消息队列的消息代理)。我选择了Kafka和RabbitMQ,特别是因为它们是受欢迎的、广泛使用的解决方案,提供丰富的功能,并在生产环境中经过了广泛的实战测试。它们被许多人认为是黄金标准。
属性 | Kafka(事件流) | RabbitMQ(消息队列) |
---|---|---|
支持的协议 | 自定义二进制协议通过TCP。 | 支持多种协议:AMQP(0-9-1和1.0),STOMP,MQTT。 |
此外,RabbitMQ可以通过插件和JMS客户端扩展以支持Java消息服务(JMS)。 | ||
消息排序 | 在分区级别有保证(分区是主题的一个段)。 | 在队列级别有保证。 |
交付语义 | 支持至少一次、至多一次,甚至恰好一次的语义(后者对于银行等行业的数据完整性至关重要)。 | 支持至少一次和至多一次的语义。没有恰好一次的交付。 |
消息优先级 | 没有本地支持。 | 支持每条消息的优先级级别,先交付高优先级的消息。 |
消息重放 | 允许多次重放消息,即使已被消费者读取。 | 没有消息重放功能。 |
死信队列 | Kafka支持死信队列的概念,对于错误处理很有用(详见此文章)。 | RabbitMQ支持死信队列,允许诊断并重新发送未能成功处理的消息。 |
路由 | 通过Kafka Connect和Kafka Streams组件可以实现高级基于内容的路由。 | 通过路由键和交换类型可以实现高级灵活的路由能力。 |
内置流处理 | 是的(Kafka Streams)。 | 没有内置的能力。 |
消息消费 | 消费者使用拉模型(长轮询)来读取消息。 | 消费者可以拉取消息,或者代理可以推送它们(推送模型是推荐的选项)。 |
代理和消费者类型 | Dump 代理,聪明的消费者。 | 聪明的代理,Dump 的消费者。 |
数据持久性 | 提供长期持久性,可以无限期保留数据。 | 队列仅保留消息,直到被消费者传递和处理。 |
可扩展性 | 每天可达数万亿条消息,分成数千个主题,分为数万个分区和数百个代理。 | 可扩展,但不设计为与Kafka相同级别的可扩展性。更适用于小型和中型部署和工作负载。 |
性能 | 每秒可达数百万条消息和多G比特的数据,延迟保持一致地低(在单位毫秒范围内)。 | 优化以处理较低的吞吐量(每秒数千或数万条消息)。可以提供非常低的延迟(可与Kafka相媲美),但在高吞吐量工作负载下延迟会增加。 |
上表只是对比了Kafka的事件流处理能力和RabbitMQ的消息队列的简要比较,总结了它们之间的基本区别。然而,如果您想更深入地了解这两种技术的比较(包括其他标准,如架构、开发者体验和生态系统),请查看这篇Kafka与RabbitMQ博文。
在需要系统不同部分之间解耦、异步通信的场景中,消息队列和事件流都可以使用。例如,在微服务架构中,两者都可以为各个组件之间提供低延迟的消息传递。然而,超越消息传递,事件流和消息队列有各自的优势,适用于不同的使用场景。
消息队列技术通常适用于:
- 不同语言编写的组件之间以及“使用”不同协议之间的通信。像RabbitMQ和ActiveMQ这样的消息队列解决方案通过支持多种协议和编程语言,实现了这一点。
- 需要复杂消息路由的用例(例如,股票交易平台根据股票类型和订单大小将买卖订单路由到不同的处理队列)。
- 在工作节点之间分发任务,其中每个任务仅由单个消费者处理一次。
- 处理频繁断开连接的消费者。在这些情况下,消息队列系统是一个不错的选择,因为它具有消息排序、临时持久性和消息重发的功能。
全球数千家公司已经将消息队列技术纳入其技术堆栈。例如,您可以查看RabbitMQ Summit网站,了解各种形状和大小的组织如何在生产中使用RabbitMQ消息队列。网站上有许多讨论供您浏览(只需点击“过去的活动”下拉菜单,然后选择您选择的峰会版本,即可查看所有相关讨论)。
现在,让我们转向事件流处理,这非常适合于:
- 收集、持久化和传输大量的事件流,例如点击流数据、股票市场行情和来自物联网设备和传感器的高频读数。
- 持续处理和分析数据,提供可操作的见解,并支持实时决策(例如,分析金融交易发生时以尽快识别欺诈行为并加以缓解)。
- 事件溯源。在这种情况下,像Kafka这样的技术是理想的选择,因为其不可变和仅追加的日志结构确保了事件的可靠、有序和可重放的记录。这使得完整的历史状态变更序列可以被存储和查询。
- 日志聚合用例。事件流处理解决方案是一个合适的选择,因为它们通常提供良好的性能、强大的耐久性保证和低延迟。此外,事件流处理技术通常与许多其他系统集成(或提供直接的集成方式),使得方便地从不同组件摄取日志数据。
有无数公司正在利用事件流处理。例如,一些大型组织,包括Uber、PayPal和Netflix,已经分享了他们如何以及为何使用Kafka以及他们所获得的好处。阅读他们的经验是值得的。但不仅大型企业依赖事件流处理。请查看我的上一篇博文,了解中小型公司如何利用Kafka的事件流处理能力。
请注意,一些事件驱动架构同时使用事件流处理和消息队列。例如,在电子商务平台的情况下,您可以使用事件流处理实时收集和分析用户的点击流数据,以便向他们提供相关的横幅,提供折扣和产品推荐。与此同时,消息队列解决方案可以用于对订单进行排队,以便进行支付和处理。
对于许多消息传递用例,消息队列是一个不错的选择。如果您在事件驱动的旅程早期,那么消息队列技术通常比事件流处理解决方案更容易部署和管理,因此具有吸引力。然而,尽管增加了额外的复杂性,但组织有时会从消息队列迁移到事件流。主要原因?可扩展性、可靠性和性能。
这正是DoorDash、AppDirect和全球支付提供商的经历。它们最初都使用了消息队列技术,具体来说是RabbitMQ和ActiveMQ。然而,面对不断增长的数据量,这些消息队列系统经历了严重的可扩展性、可靠性和性能问题。所有三家公司最终不得不重新评估其技术堆栈,并用Kafka的事件流处理能力替换了RabbitMQ/ActiveMQ。通过转向Kafka,这三个组织显著提高了系统的正常运行时间、可扩展性、可用性和性能(更低的延迟和更高的吞吐量)。
我很好奇未来是否会有更多企业继续从消息队列转向事件流。另一个可能的趋势是公司将从一开始就采用事件流平台,尤其是考虑到引入了Kafka的队列计划。换句话说,Kafka最终可能成为一种同样适用于事件流使用案例和传统消息队列场景的技术(目前,将Kafka用作传统消息队列具有挑战性——详见这篇文章的详细信息)。
如果您处理的是小型和中型工作负载,希望在组件之间可靠而灵活地路由消息,并且您的系统主要关注当前状态,那么消息队列技术是一个合适的选择。
另一方面,如果您希望以可扩展且可靠的方式处理大容量、高频率的事件流,并且需要在数据到达时进行复杂处理以获取实时见解,且您的系统不仅关注当前状态,还关注状态变更的历史记录,那么事件流处理是正确的选择。
正如我们所见,有时公司开始仅使用消息队列,然后迁移到事件流技术。这种迁移非常困难且耗时。因此,如果您在事件驱动的旅程中处于早期阶段,并且正在思考事件流或消息队列对您是否是正确选择,请问自己:当前的需求是否两者都可以同样满足?如果答案是肯定的,那么我建议您选择事件流。它是未来更强大、更可靠的基础。
的确,与消息队列相比,事件流工具通常更难学习和管理。但不要因此而灰心。托管平台如Confluent Cloud和Redpanda大大简化了处理事件流的工作。此外,它们与Quix等无服务器流处理解决方案无缝配合,使您能够轻松构建、部署和监控从实时数据中提取价值的事件流应用。
查看这些交互式模板,了解通过将Confluent Cloud/Kafka/Redpanda作为流传输和Quix作为流处理引擎相结合,您可以创建哪些事件驱动应用程序。