事件驱动微服务架构的最佳实践

2025-05-24

事件驱动微服务架构的最佳实践

如果您是企业架构师,您可能听说过并使用过微服务架构。虽然您过去可能使用过 REST 作为服务通信层,但越来越多的项目正在转向事件驱动架构。让我们深入探讨这种流行架构的优缺点、它包含的一些关键设计选择以及常见的反模式。

什么是事件驱动的微服务架构?

在事件驱动架构中,当一个服务执行其他服务可能感兴趣的某项工作时,该服务会生成一个事件——即已执行操作的记录。其他服务会消费这些事件,以便执行该事件所需的任何自身任务。与 REST 不同,创建请求的服务不需要了解消费请求的服务的详细信息。

这是一个简单的例子:当在电子商务网站上下订单时,会产生一个“已下订单”事件,然后由多个微服务使用:

1)订单服务,可以将订单记录写入数据库;
2)客户服务,可以创建客户记录;
3)支付服务,可以处理支付。

事件可以通过多种方式发布。例如,可以发布到队列中,以确保事件交付给合适的消费者;也可以发布到“发布/订阅”模型流中,该流发布事件并允许所有相关方访问。无论哪种方式,生产者都会发布事件,消费者会接收该事件并做出相应的响应。请注意,在某些情况下,这两个参与者也可以称为发布者(生产者)和订阅者(​​消费者)。

为什么要使用事件驱动架构

事件驱动架构比 REST 具有几个优势,包括:

  • 异步——基于事件的架构是异步的,无阻塞。这使得资源在工作单元完成后可以自由地转移到下一个任务,而无需担心之前发生或接下来会发生什么。它们还允许事件排队或缓冲,从而防止消费者对生产者施加压力或阻塞生产者。

  • 松耦合 – 服务不需要(也不应该)了解或依赖其他服务。使用事件时,服务可以独立运行,无需了解其他服务,包括其实现细节和传输协议。事件模型下的服务可以独立且更轻松地进行更新、测试和部署。

  • 易于扩展——由于服务在事件驱动架构下解耦,并且服务通常只执行一项任务,因此追踪特定服务的瓶颈并扩展该服务(且仅扩展该服务)变得容易。

  • 恢复支持——带有队列的事件驱动架构可以通过“重放”过去的事件来恢复丢失的工作。当消费者需要恢复时,这对于防止数据丢失非常有用。

当然,事件驱动架构也有缺点。它们很容易过度设计,因为会分离那些在紧密耦合时可能更简单的关注点;可能需要大量的前期投资;并且通常会导致基础设施、服务契约或模式、多语言构建系统和依赖关系图的额外复杂性。

或许最大的缺点和挑战在于数据和事务管理。由于事件驱动模型的异步特性,它们必须谨慎处理服务间数据不一致、版本不兼容、重复事件监控等问题,并且通常不支持 ACID 事务,而是支持最终一致性,而这会增加跟踪或调试的难度。

即使存在这些缺点,事件驱动架构通常仍然是企业级微服务系统的更好选择。其优点——可扩展、松耦合、对开发运维友好的设计——超过了缺点。

何时使用 REST

然而,有时 REST/Web 界面可能仍然是更好的选择:

  • 您需要一个同步请求/回复接口
  • 您需要便捷的支持以实现强劲的交易
  • 您的 API 已向公众开放
  • 您的项目很小(REST 的设置和部署要简单得多)

您最重要的设计选择——消息传递框架

决定采用事件驱动架构后,就该选择事件框架了。事件的生成和消费方式是系统的关键因素。市面上有数十种经过验证的框架和选择,选择合适的框架需要时间和研究。

您的基本选择归结为消息处理或流处理。

消息处理

在传统的消息处理中,组件会创建一条消息,然后将其发送到特定的(通常是单个的)目标。接收组件一直处于空闲状态并等待接收消息,然后执行相应的操作。通常,当消息到达时,接收组件会执行单个处理。然后,该消息会被删除。

消息处理架构的一个典型示例是消息队列。虽然大多数较新的项目都使用流处理(如下所述),但使用消息(或事件)队列的架构仍然很流行。消息队列通常使用代理的“存储转发”系统,事件在代理之间传递,直到到达合适的消费者。ActiveMQRabbitMQ是两个流行的消息队列框架示例。这两个项目都拥有多年的实践经验和成熟的社区。

流处理

另一方面,在流处理中,组件在达到特定状态时会发出事件。其他感兴趣的组件会在事件流上监听这些事件并做出相应的反应。事件并非针对特定的接收者,而是所有感兴趣的组件均可访问。

在流处理中,组件可以同时对多个事件做出反应,并对多个流和事件应用复杂的操作。某些流具有持久性,事件会根据需要在流中保留。

通过流处理,系统可以重现事件历史记录,在事件发生后恢复运行并做出响应,甚至执行滑动窗口计算。例如,它可以根据每秒事件流计算出每分钟的平均 CPU 使用率。

Apache Kafka是最流行的流处理框架之一。Kafka 是一个成熟稳定的解决方案,已被许多项目采用。它可以被视为一个可靠的、工业级的流处理解决方案。Kafka 拥有庞大的用户群、活跃的社区和完善的工具集。

其他选择

还有一些其他框架,它们要么提供流和消息处理的组合,要么提供自己独特的解决方案。例如, Apache 的新产品Pulsar,是一个开源的发布/订阅消息系统,同时支持流和事件队列,并且都具有极高的性能。Pulsar 功能丰富——它提供多租户和地理复制——因此也比较复杂。有人说,Kafka 的目标是高吞吐量,而 Pulsar 的目标是低延迟。

NATS是一种采用“合成”队列的替代发布/订阅消息系统。NATS 专为发送小规模、频繁的消息而设计。它兼具高性能和低延迟。然而,NATS 认为一定程度的数据丢失是可以接受的,因此优先考虑性能而非投递保证。

其他设计考虑

一旦选择了事件框架,还需要考虑以下几个其他挑战:

  • 事件溯源

    实现松耦合服务、独立数据存储和原子事务的组合非常困难。一种可能有用的模式是事件溯源。在事件溯源中,更新和删除操作不会直接作用于数据;相反,实体的状态变化会被保存为一系列事件。

  • CQRS

    上述事件溯源引入了另一个问题:由于状态需要从一系列事件构建,查询可能会很慢且复杂。命令查询职责分离 ( CQRS ) 是一种设计解决方案,它要求为插入操作和读取操作分别建立单独的模型。

  • 发现事件信息

    事件驱动架构的最大挑战之一是对服务和事件进行编目。如何找到事件描述和详细信息?事件发生的原因是什么?哪个团队创建了该事件?他们是否正在积极地处理此事?

  • 应对变化

    事件模式会改变吗?如何在不破坏其他服务的情况下更改事件模式?随着服务和事件数量的增长,如何回答这些问题变得至关重要。

    成为一个优秀的事件消费者意味着要为变化的模式编写代码。成为一个优秀的事件生产者意味着要意识到模式的更改如何影响其他服务,并创建设计良好且记录清晰的事件。

  • 本地部署 vs 托管部署

    无论您的事件框架是什么,您还需要决定是在内部自行部署框架(消息代理的操作并不容易,尤其是在高可用性的情况下),还是使用托管服务(例如Heroku 上的 Apache Kafka)。

反模式

与大多数架构一样,事件驱动架构也存在一些反模式。以下是一些需要注意的。

  • 好事太多

    注意,不要对创建事件过于兴奋。创建过多的事件会在服务之间造成不必要的复杂性,增加开发人员的认知负担,使部署和测试更加困难,并导致事件使用者的拥堵。并非每个方法都需要成为事件。

  • 通用事件

    请勿使用通用事件,无论其名称还是用途。您希望其他团队了解您的事件存在的原因、用途以及使用时机。事件应具有特定的用途并采用相应的命名。名称通用的事件或带有令人困惑的标志的通用事件会导致问题。

  • 复杂的依赖图

    小心那些相互依赖并创建复杂依赖图或反馈循环的服务。每一次网络跃点都会给原始请求增加额外的延迟,尤其是离开数据中心的南北向网络流量。

  • 取决于保证的顺序、交付或副作用

    事件是异步的;因此,包含顺序或重复的假设不仅会增加复杂性,还会抵消基于事件的架构的许多关键优势。如果您的消费者有副作用,例如在数据库中添加值,那么您可能无法通过重放事件来恢复。

  • 过早优化

    大多数产品都是从小规模开始,随着时间的推移而发展壮大。虽然您可能梦想着未来扩展到大型复杂的组织,但如果您的团队规模较小,那么事件驱动架构所带来的复杂性实际上可能会拖慢您的速度。相反,您可以考虑采用简单的架构设计您的系统,但要包含必要的关注点分离,以便随着需求的增长进行替换。

  • 期望事件驱动解决所有问题

    从技术层面来说,不要指望事件驱动架构能够解决所有问题。虽然这种架构确实可以改善许多技术缺陷,但它无法解决核心问题,例如缺乏自动化测试、团队沟通不畅或开发运维实践过时。

了解更多

了解事件驱动架构的优缺点以及一些最常见的设计决策和挑战是创建最佳设计的重要部分。

如果您想了解更多信息,请查看这个事件驱动的参考架构,它允许您一键在 Heroku 上部署一个工作项目。该参考架构创建了一个销售虚构咖啡产品的网上商店。

精选咖啡

产品点击事件会被跟踪并存储在 Kafka 中,然后被报告仪表板使用。

按钮点击

该代码是开源的,因此您可以根据需要修改它并运行自己的实验。

文章来源:https://dev.to/heroku/best-practices-for-event-driven-microservice-architecture-2lh7
PREV
我放弃注册后收到了邮件。我没有点击“提交”。这很不妥。
NEXT
使用 JavaScript 验证电子邮件的 4 种方法📮