事件驱动架构如何解决现代web应用程序的问题

发布于:2020-12-24 16:16:13

0

53

0

web开发 EDA 后端架构 事件驱动架构 事件

自从我们习惯于从服务器上提供静态HTML内容以来,基于web的应用程序已经取得了很大的进步。如今,应用程序更加复杂,并且使用多个框架、数据中心和技术。在过去的几年里,我们看到了两个主导IT市场的概念:

            1.将我们的应用程序转移到云端;

            2.实现微服务体系结构;

这些想法塑造了我们设计和构建软件的方式。在某种程度上,我们不再构建应用程序;相反,我们正在构建平台。应用程序不再共享相同的计算空间。相反,它们必须通过轻量级通信协议(如REST api或RPC调用)彼此通信。这种模式使得创建一些令人惊叹的软件成为可能,比如Facebook、Netflix、Uber等等。

在本文中,我们将讨论现代web开发中推动创新的一些问题。然后我们将深入事件驱动架构(EDA)的基础知识,EDA试图通过以一种新的方式思考后端架构来解决这些问题。

现代网络面临的问题

每一种技术都必须处理挑战,总是开机,多用户,异步应用面临的今天:

可用性

我们现在有许多、几十个甚至数百个链接服务,而不是一个应用程序,而且每个服务都必须全天候待命。我们怎么做呢?大多数情况下,服务被水平伸缩到多个实例,有时跨越多个数据中心,使其高度可用。进入此特定服务的所有请求将在所有实例中均匀路由。一些部署工具提供自修复功能,因此如果一个实例宕机,它将创建另一个实例来替代它。

可伸缩性

可伸缩性与可用性有很多共同之处。可用性就是确保至少有一个服务实例启动并运行,准备为传入的请求提供服务。另一方面,可伸缩性关注的是性能。如果一个应用程序超载,那么我们可以创建该应用程序的新实例来适应增加的请求数量。但是扩展应用程序并不是没有挑战的,特别是当我们处理有状态应用程序时。

单一真实来源

在微型服务出现之前,这项工作很简单。所有数据都驻留在一个地方,通常是某种关系数据库。但是,当多个服务共享一个数据库时,您可能会产生问题,比如团队之间对模式更改的依赖关系或性能问题。解决这个问题的一种常见模式是为每个服务使用一个数据库。一个分布式的真相来源确实有助于维护干净的架构,但是我们现在必须处理分布式事务和维护多个数据库的复杂性。

同步

在典型的请求-响应场景中,客户端等待服务器响应;它会阻塞所有活动,直到收到响应或超时结束。如果我们接受这种行为,并使用跨系统的链式调用将其放入微服务体系结构中,我们很容易就会出现我所称的“微服务地狱”。“每件事都从一个服务调用开始,我们就叫它服务A吧。但之后,服务A需要调用服务B,有趣的事情就开始了。”这种行为的问题是,如果一个服务有资源被阻塞(例如一个线程挂起),超时现在是指数级的。如果我们允许每个服务的超时时间为500毫秒,并且链中有5个服务调用,那么第一个服务的超时时间需要为2500毫秒(2.5秒),而最后一个服务的超时时间需要为500毫秒。

引入事件驱动架构

在经典的三层应用程序中,系统的核心是数据(基础)。在EDA中,焦点转移到事件以及它们如何在系统中流动。这种转变使我们能够完全改变设计解决上述问题的应用程序的方式。

在实际看到EDA是如何做到这一点之前,让我们先看看事件到底是什么。事件是在应用程序状态中触发通知或某种更改的动作。一盏灯已经打开(通知),温控器关闭了加热系统(通知),一个用户更改了他的地址(状态更改),或者你的一个朋友更改了他的电话号码(状态更改)。所有这些都是事件,但这并不意味着我们应该将它们添加到事件驱动的解决方案中。要添加一个事件,它必须与业务相关。用户下了新订单是与该特定业务相关的事件,但他/她午餐吃披萨则不是。

当你想到那些与企业相关的事件时,它们可能是显而易见的,但有些可能不是。特别是那些作为对其他事件的反应而发生的事件。要发现在系统中流动的事件,使用一种叫做事件风暴的技术。将应用程序的涉众聚集在一起(从软件工程师到业务人员和领域专家),并将所有业务流程作为特定事件来规划。在映射了所有业务流程之后,工程团队可以将其结果用作构建其应用程序的需求。了解了什么是事件以及如何识别它们之后,让我们看看它们如何解决前面提到的常见问题。

事件以单一方向流动,从生产者到消费者。将其与REST调用进行比较。事件生成器从不期望来自使用者的响应,而在REST调用中总会有响应。没有响应,不需要阻止代码执行,直到发生其他事情。这使得事件本质上是异步的,完全消除了超时运行的风险。

事件的发生是动作的结果,因此没有目标系统;我们不能说服务A触发事件到服务B;我们可以说,服务B对服务a产生的事件感兴趣,但可能还有其他方也感兴趣,比如服务C或服务D。

那么,我们如何确保由一个系统触发的事件能够到达所有感兴趣的服务呢?大多数情况下,这个问题由消息代理解决。代理只不过是一个应用程序,充当事件生成器(创建事件的应用程序)和事件使用者之间的中间人。通过这种方式,应用程序可以很好地解耦,以解决我在文章前面谈到的可用性问题。如果一个应用程序暂时不可用,当它重新联机时,它将开始使用事件并处理它们,赶上当应用程序宕机时触发的所有事件。

存储呢?事件是否可以存储在数据库中,或者是否存在其他的存储方式?事件当然可以存储在数据库中,但是这样做会丢失它们的“事件”方面。事件发生后,我们不能改变它,所以事件是不可变的。另一方面,数据库……它们是可变的,我们实际上可以在插入数据后更改数据。

存储事件的更好方法是使用事件日志。事件日志只不过是一个集中的数据存储,其中每个事件都存储为一系列不可变的记录,也称为日志。将日志想象成一个日志,其中每个新事件都附加到列表的末尾。我们总是可以通过重播日志中从开始到现在的所有事件来重新创建最新的状态。

我还没有谈到的惟一一点是可伸缩性。使用事件驱动思维构建的服务被设计为部署在多实例场景中。由于状态本身存储在事件日志中,因此服务本身将是无状态的,这允许我们对任何特定的服务进行扩展。

此模式的唯一例外是必须创建物化视图的服务。本质上,物化视图表示事件日志中某个时间点的状态。使用这种方法可以更方便地查询数据。回到可伸缩性问题,物化视图只不过是以表的格式聚合的事件,但是我们应该将这些表存储在哪里呢?大多数情况下,我们看到在内存中执行这些聚合,它们会自动将我们的服务转换为有状态的服务。一个快速而简单的解决方案是向创建物化视图的每个服务添加一个本地数据库。通过这种方式,状态存储在数据库中,服务再次是无状态的。

尽管事件驱动的架构已经存在了15年多,但是直到最近它才获得了巨大的流行,这是有原因的。大多数公司都在经历“数字化转型”阶段,随之而来的是疯狂的需求。这些需求的复杂性迫使工程师采用新的设计软件的方法,即减少服务之间的耦合和降低维护开销的方法。EDA是解决这些问题的一种方法,但不是唯一的方法。另外,您不应该期望通过采用EDA可以解决所有问题。有些特性可能仍然需要好的老式同步REST api或在关系数据库中存储数据。检查什么是最适合你的,并适当地设计它!