使用Apache Kafka实现从单体到事件驱动微服务 - swlh


我们将设计一个基于经典遗留应用程序的进化事件驱动系统,类似于在世界各地的许多组织中可以找到的系统。这个练习将向我们展示事件驱动架构的潜力。
 
消息驱动与事件驱动区别
让我们考虑两个需要通过信号相互传递信息的松散耦合组件。在这两种范式中,组件异步传递信号,允许它们在不等待响应的情况下传输信息。细微的区别在于发送这些信号背后的意图,以及它们的最终目的地:

  • 事件是向事件存储区提出的简单通知
  • 消息 是发送给可寻址接收者的数据

消息背后的意图是向某人传达某些信息。对于事件,它只是为了交流一些东西。就是这样!
消息的接收者会随着时间的推移而改变。事件的目的地始终保持不变——它是事件存储。
这就是强大的事件驱动方法如何实现真正的松耦合。
(banq注:消息是上下文敏感的,和上下文有关,一个消息就像一条河里的一朵浪花,说明不了什么;而一则事件则可以说明问题,更强调行为动作,上下文变成隐式背景了。)
 
分布式事件驱动系统中发生的所有交互都浓缩为三种类型:
  • 事件:对系统某些事实的观察。是否会触发副作用。
  • 命令:必须使系统做某事的事件。必须触发副作用。
  • 查询:查看系统内部状态的请求。一定不会引发副作用。

副作用会改变系统的内部状态——更新数据库、强制业务流程执行等。
我们很少期望通过查看数据库中的数据来更改系统中的某些内容,因为这样的操作没有副作用。

进化的单体
这是我们的患者:一个名为CRUX的单体应用程序。它是紧耦合遗留应用程序的经典示例。
它有一些 RESTful API,以及一个 Apache Kafka 的集成实例,可以将一些事件数据推送到 AWS S3。
我们会将其转变为成熟的、事件驱动的微服务架构。
 

  • 进化第一步:解耦微服务

1.释放查询
首先,我们将简单地在已经开发和运行的 API 上使用Event-Carried State Transfer 模式,以提高它们的读取速度并做一些初始准备工作。
部署在CRUX旁边的 Redis 数据库的各个状态将使用Apache Kafka 实例传输到服务的本地状态存储,从现在开始称为事件存储。
现在通过快速且廉价的本地数据查找来提供查询服务。其他一切仍然通过CRUX运行。微服务的状态通过 Event Store 的 State Transfer不断更新。
在这个阶段的早期,我们应该对来自CRUX 的事件应用事件溯源到状态重播的微服务。开发需要一段时间,但事件溯源是一个强大的工具,可以让您在路上掌握事件。这很值得!
在实践中,Confluent 提供了各种连接器应用程序,可以帮助您相当轻松地将 Event Store 与服务数据库连接起来。

2. 发布命令
接下来,需要弃用传统的 WRITE 通道。我们希望我们的微服务能够控制它们的状态——我们可以通过Notification Events来实现。
微服务应该生成关于它们向事件存储的状态更改的通知,再次触发状态传输并更新它们的状态存储以及CRUX Legacy 数据库。
这里诞生了我们的第一个事件驱动的微服务:)
在查看微服务状态时,我们发送一个 Query,并接收返回的视图。当改变那个状态时,我们发送一个描述状态改变的命令,然后立即应用到微服务数据库。这种模式称为命令查询职责分离
状态更改仍在传输回CRUX。数据不再归单体所有,但依赖于它的剩余功能仍然满足。
这就是我们看到最终一致性在起作用的地方——我们希望在更新事件之后,微服务的状态最终将与 CRUX 中的状态相等,将所有内容保持在一起。总的来说,这是对事件驱动架构的主要批评,出于某种原因,这让人们非常不舒服。根据我的经验,以及与我交谈过的其他人,您很少需要严格一致性,因此这只是将可能发生的事情作为现实的一部分来接受的问题。但我们暂时不要专注于此。

  • 进化第二步骤:扼杀单体

驻留在 CRUX 中的功能应该按照Strangler 模式逐步转移到各自的事件驱动微服务中。
我们可以使用事件协作模式来设计跨越多个服务生成不同类型事件的工作流。
我们还可以包含指向由其他事件触发的事件的指针,允许定位先前处理的事件。
在协作微服务覆盖了所有功能之后,CRUX 可以作为故障转移机制保持运行(以防我们的一个新微服务失效)或完全弃用。
 

  • 进化第三步骤:上下文化微服务

微服务需要放入各自的Bounded Contexts有界上下文,然后拆分为 Query 和 Command 端组件。
在每个上下文中,都应该有一个系统事件存储,只能由其中的服务访问。它应该包含上下文中的协作成功所需的上下文微服务生成的所有事件。每当发生需要更新域特定状态的里程碑事件时,应生成域事件并将其发送到域事件存储。
此类事件对领域专家意义重大。这是将我们的应用程序状态保持在一起的数据,使系统最终跨上下文保持一致。领域事件应该比系统事件更重要,并且它们的架构很少改变。
 
应用事件驱动进化的好处
  • 新架构的组件松散耦合且位置透明。
  • 域和系统事件的分离将不需要的数据耦合的风险降至最低。这种划分对于识别业务领域所需的核心数据耦合很重要。
  • 由此产生的可插拔架构专为变化而设计,使软件开发人员能够专注于在不断变化的敏捷环境中快速提供业务功能。
  • 引入新的微服务和上下文没有副作用,并且不会中断系统中任何其他组件的工作。
  • 得益于CQRS模式,用于写入和读取的分离组件可以独立优化和扩展,最大限度地减少资源使用并在重负载下保持良好的响应能力。
  • 事件溯源提供完整的系统审计,提高系统透明度并提供收集客户行为洞察的方法。系统配备了所有历史变化的不可变日志,为进一步处理提供历史数据。
  • 最后,微服务之间的关注点的明确分离导致更简单的依赖树、更直观和易于理解的代码库,从而加快编码和项目交付时间。
  • 由于组件的隔离,解耦架构提供了为给定要求选择更合适的技术的可能性。
  • 上下文具有明确的边界和职责,这使得组织的可扩展性和工作分工更加精简。