译自:Introduction to Event-Driven Architecture
后面将引入几篇与EDA相关的文章,目的在于充分掌握EDA架构的优劣势。
在前面的微服务介绍一文中讨论了服务的颗粒度,以及保证松耦合的必要性。文中提出,服务应该是自治且完整独立的,并尽量减少同步通信。今天,我们将讨论松耦合意味着什么,并探索一种在微服务社区中越来越受欢迎的"交易技巧"-事件驱动架构。
事件驱动架构(EDA)是一个促进生产和消费事件的软件架构规范。
一个事件表示一个感兴趣的动作。通常,事件对应一个创建或修改某些实体状态的动作。例如,在电子商务应用程序中下订单是一个事件,分发一个已下单的产品也是一个事件。一个消费者提交一个对接收的产品的评论也是一个事件。
关于事件的奇特之处在于它们不会明确地传达给可能关心它们的特定服务。事件"只会发生"。更为重要的是事件只会单纯地发生,与是否存在关心这些事件的特定服务无关。这听起来像是经常被引用的哲学思想:"如果一颗森林中的树,没有人听到它,那么它会发出声音吗?"。但这也是事件之所以强大的原因--事件会转换为一条对某些正在发生的事情的(自包含)记录,事件及其扩展程序(从根本上讲)与它们的处理程序是分离的。实际上,事件记录的生产者并不知道消费者是谁,甚至不知道是否存在消费者。
一条记录通常包含描述一个事件的信息。在之前的订单为例,其对应的事件的JSON描述如下:
{
"orderId": "760b5301-295f-4fec-95f8-6b303a3b824a",
"customerId": 28623823,
"productId": 31334,
"quantity": 1,
"timestamp": "2021-02-09T11:12:17+0000"
}
Node:尽管记录和事件存在细微的差别,但它们经常可以互换,即术语"事件"通常指代一个事件的"记录",为了简化描述,本文中将自由地使用这两个术语。
上述是对一个订单的高度简化。发起订单(购物车服务)的应用并不知道谁(如何,以及为什么)处理该订单。生产者会保证潜在的消费者能够捕获处理事件所需的一切信息。也就是说,订单记录不一定严格包含实现订单所需的每个属性。例如,不一定会直接指定产品的尺寸,存放位置以及消费者的送货地址等信息,但可以解析通过捕获的订单记录中的ID间接获得这些信息。关系数据库中的外键概念也同样适用于事件。
如果生产者和消费者都互不感知对方,那么两者该如何通信?
答案是通过术语"记录"进行粘合。事件通常被持久化到一个众所周知的位置,称为日志(有时也会用到术语"账簿")。日志是底层的,只能在后续消费者可以访问的地方附加生产者保存的事件数据结构。brokers(位于生产者和消费者之间的持久化中间件)负责操作日志。一旦产生了一个事件,任何人都可以消费该事件。
当处理事件驱动系统时,我们经常会使用术语"流"来描述一个或多个日志接口。日志是物理上的概念(使用文件实现),一条流是逻辑上的概念,表示构成事件的一组没无边界的记录,但记录要遵守某种特定的顺序。不同的流平台可能使用专有名称指代一条流。Apache Kafka使用topics和partitions来描述流。
生产者、消费者和流的关系如下:
Event-Driven Architecture Reference Model
回顾一下相关概念:
为什么EDA能够大大降低耦合度?
对耦合的一种比较务实的定义是:一个组件受其他组件影响的程度。耦合存在于空间(组件在结构上相关联)和时间(时间会影响组件之间的关系程度)上。对于后者,一个比较好的例子是,一个服务同步调用其他服务的REST API。如果被调用的服务down,则该服务将无法继续处理(响应被阻塞)。如果两个服务必须同时运行,则二者之间会存在一定程度的临时耦合(temporal coupling)。如果两个服务高度依赖,则称之为强耦合,反之,则称为松耦合。
Conceptual model of coupling
EDA采用两种方法来抑制耦合。
EDA并不是银弹,它没有一并消除耦合的概念(否则,系统中的组件将不再共同作用)。现在将关注点转移到broker上:为了让生产者和消费者有意义地进行解耦,它们必须依赖一个broker。这种方式增加了系统架构的复杂度,并引入了其他故障点。这也是为什么brokers必须是高性能且具有容错能力,否则,我们只是将一组问题换成另一组。
时间处理通常分为三种常用的方式。这些方式并不互斥,它们经常会同时存在于一个大型的事件驱动系统中。
用于处理离散事件:例如在社交媒体平台上发布一个帖子。离散事件处理的特征在于出现的事件之间通常并无关联,可以独立处理。
用于处理一系列相关联的无边界事件流,事件的记录以某种顺序呈现,并携带一些与发生的事件有关的信息。例如,当一个业务实体发生联合变更时,消费者可能会按照生产者指定的顺序进行变更,并在本地数据库中保存一份该实体的副本。由于需要关注事件处理的顺序,因此不能离散地处理这类事件。消费者需要避免条件竞争,即多个消费者实例可能会同时修改数据库中的某条记录,进而由于乱序更新而导致数据不一致。
比较有名的流事件平台,如Kafka会依赖记录的key和partitions来保留更新顺序。Kafka同时也保证对一个实体的所有变更会被某个消费者处理,避免多个消费者并行处理事件而导致并发竞争。
复杂事件处理(CEP)是一种从一系列简单事件中得出或识别复杂事件的模式。例如监控一座建筑内的温度和延误感应器,并于推断是否发生了火情,并进行持续跟踪。单独的温度变化并不足以引发报警。更具意义的是温度峰值和变化率聚合而成的群体事件,进而有可能挽救生命。
通常更多会涉及此类处理,要求事件处理器持续跟踪先前的事件,并提供一个有效的方式进行请求和聚合。
一些场景下可以使用事件驱动架构带来的优势:
EDA不是万能药,与很多强大的工具一样,它有可能被错误地使用。下面列出的内容不应该被认为是EDA的缺点,而应该作为开发人员和架构师在设计和实现事件驱动的系统时应注意的一系列陷阱。
复杂的编排。使用松耦合组件,用户可能会感到困惑,整个架构看起来像是一个Rube Goldburg机器(可以借助下图理解Rube Goldburg),整个业务逻辑也被实现为一系列(带有副作用的包装的)事件:一个组件发起的事件可能触发另一个组件发起另一个事件,然后触发另一个组件发起事件,以此类推。这种组件间的交互很快会变得无法理解。
将命令和事件混淆。一个事件用于单纯地描述发生的事情。它不会指定如何处理事件。而一个命令是针对特定组件的直接指令。由于命令和事件都是某种类型的消息,非常容易混淆,把命令误以为是一个事件。
命令也可以放到EDA下,但要分清与事件的区别。命令可能会修改系统状态,通常会需要回滚方案。
消费者不可知。事件应该以某种方式捕获相关的属性,但并不会限制如何处理这些事件。说起来容易,做起来难。有时我们可能会无法获得足够的信息来限制添加到事件记录的内容(无法确定这些添加到记录中的信息是否最终有用)。
个人认为最重要的是上面的第二点,要区分命令和事件。
微服务架构模式是构建更可维护、可扩展、更健壮的软件系统所涉及的难题之一。从问题分解的角度来看,微服务非常棒,但也带来了很多棘手的问题,其中一个就是耦合。与一开始相比,随意将系统拆分为少数微服务的做法可能会使您处于更糟糕的局面。有一个术语可以对其进行描述"分布一体式"。
为了帮助解决困惑,并定位耦合的问题,我们引入了事件驱动架构。
EDA是一个可以帮助降低系统组件间的耦合的有效工具,它是一种使用生产者、消费者、事件和流进行交互的模型。一个事件表示一个感兴趣的动作,任何组件都可能异步地发布和消费事件,而无需感知对方的存在。EDA允许组件独立操作和演化。但它不是解决所有问题的银弹。EDA是一个不错的选择,它带来的好处大大超过了采用它的成本。可以说,EDA是成功部署微服务的必要要素。
原文:https://www.cnblogs.com/charlieroro/p/14439895.html