事件驱动架构(七)

事件驱动架构

事件驱动架构是一种体系结构风格,其中系统中的组件发出事件并对事件作出反应。组件A在事件发生时直接调用组件B,而不是组件A发出事件。组件A不知道哪些组件监听它的事件。

事件驱动架构既在一个进程内部使用,又在进程之间使用。例如,GUI框架通常使用事件很多。另外,在我的教程中关于并发模型的汇编线并发模型(AKA,非阻塞并发模型)也使用了一个事件驱动的体系结构。

在本教程中,我将重点讨论进程间的事件驱动架构。因此,当我在本教程的其余部分中编写事件驱动架构时,这就是我所指的,即使它不是术语的唯一含义。

进程间的事件驱动体系结构

事件驱动体系结构是一种体系结构风格,其中系统的传入请求被收集到一个或多个中央事件队列中。从事件队列中,事件被转发到要处理事件的后端服务。

事件驱动体系结构有时也被称为消息驱动体系结构或流处理体系结构。事件可以看作是消息流——因此还有另外两个名字。流处理架构也被称为lambda架构。无论如何,我将继续使用名为事件驱动的体系结构。

事件队列

这是一个事件驱动的体系结构,你有一个或多个中央事件队列,在它们被处理之前,所有事件都被插入。下面是事件驱动架构与事件队列的简单说明:


事件日志

写入事件队列的消息可以写入事件日志(通常在磁盘上)。如果系统崩溃,系统可以通过重放事件日志来重建它的状态。下面是事件驱动架构的示例,其中事件日志用于保存事件:

还可以备份事件日志,从而获取系统状态的备份。在实际部署到生产之前,可以使用此备份在新版本上运行性能测试。或者,您可以重放事件日志的备份,以重现已报告的某些错误。

事件收集器

请求通常通过网络到达,例如作为HTTP请求或通过其他协议。事件通过事件收集器从它们的许多来源接收。下面是事件驱动架构的示例,其中添加了事件收集器:


应答队列

有时,您可能需要向请求(事件)发送回复。因此,一些事件驱动的体系结构也有一个应答队列。下面是一个事件驱动架构的图,它使用事件队列(入站队列)和应答队列(出站队列):


如您所见,答复可能必须被路由回正确的事件收集器。例如,如果HTTP收集器(Web服务器本质)将通过HTTP接收的请求发送到事件队列中,则为该事件生成的答复可能必须再次通过HTTP收集器(服务器)发送回客户端。

通常,应答队列不被持久化,这意味着它没有写入事件日志。只有传入事件持久化到事件日志。

读写事件

如果将所有传入的请求分类为事件,它们都将被推到事件队列中。如果事件队列是持久的(持续到事件日志),则意味着所有事件都被持久化。持久事件通常是慢的,所以如果我们能够过滤掉一些不需要持久化的事件,那么我们就有可能提高事件队列的性能。

事件队列被持久化到事件日志的原因是,我们可以重放事件日志,并重新创建由事件引起的系统的确切状态。为了支持这一点,只有改变系统状态的事件才需要被持久化。换句话说,可以将事件划分为读取事件和写入事件。读取事件只读取系统状态,但不改变系统状态。写入事件更改系统状态。

通过读写事件之间的事件划分,只需要保持写入事件。这将给事件队列提供性能提升。这个性能的增加到底有多大,取决于读写事件的比率。

为了将事件划分为读和写事件,在事件到达事件队列之前必须在事件收集器中进行区分。否则,事件队列无法知道给定事件是否应该被持久化。

您也可以将事件队列分割为两个。用于读取事件的一个事件队列和用于写入事件的一个事件队列。这种方式读取事件在慢写事件后面不会慢下来,并且事件队列不必检查每个消息以查看它是否应该被持久化。读取事件队列不持久事件,并且写入事件队列总是保存事件。

下面是事件驱动架构的示例,其中事件队列被分成读写事件队列:

是的,箭头看起来有点混乱,但在实践中,创建3个队列并在它们之间分配消息并不十分混乱。

事件日志重播挑战

在系统崩溃或系统重新启动的情况下,重放事件日志以重新创建系统状态的能力通常被强调为事件驱动架构的一大优点。在日志可以独立于时间和周围系统重放的情况下,这是一个很大的优势。

然而,完全独立于时间重放事件日志并不总是可能的。在下面的章节中,我将介绍一些事件日志重播的挑战。

动态值处理

如前所述,写事件是处理时可能改变系统状态的事件。有时这样的状态变化取决于在处理事件时所解析的动态值。动态值的示例可以是事件被处理的日期和时间(例如,订单日期)或在该特定日期和时间的货币汇率。

这种动态值表示对事件日志重播的挑战。如果在不同的一天重播事件日志,则服务处理事件可以解决不同的动态值,如另一个日期和时间,或另一个汇率。因此,在不同的一天重放事件日志不会导致重新生成完全相同的系统状态,就像最初处理事件一样。

为了解决动态值问题,可以让写入事件队列能够将所需的动态值标记到事件中。然而,为此,事件队列需要知道每个消息需要什么动态值。这将使事件队列的设计复杂化。每当需要一个新的动态值时,事件队列就需要知道如何查找该动态值。

另一种解决方案是写入事件队列仅用事件的日期和时间来标记写入事件。使用事件的原始日期和时间,服务处理事件可以查找给定日期和时间的动态值。例如,它可以查找当时有效的汇率。这当然要求服务可以基于日期和时间实际查找动态值,但情况并非总是如此。

与外部系统协调

事件日志回放的另一个挑战是与外部系统协调。例如,假设您的事件日志包含Web商店的产品订单。当您处理订单时,您的系统可以第一次将订单发送到外部支付网关,以从客户的信用卡收取金额。

如果稍后重播事件日志,则不希望客户端以相同的顺序再次被收取。因此,在回放过程中,不希望将订单发送到外部支付网关。

事件日志重播解决方案

解决日志重放的问题并不总是容易的。有些系统没有问题,可以按原样重放事件日志。其他系统可能需要知道原始事件的日期和时间。然而,其他系统可能需要了解更多的东西——比如在事件的原始处理过程中从外部系统获得的值。

重播模式

在任何情况下,从写入事件队列侦听事件的任何服务都必须知道传入事件是原始事件还是重放事件。这样,服务可以决定如何处理动态值的分辨率和与外部系统的协调。

多步事件队列
事件日志重播挑战的另一个解决方案是具有多步事件队列。步骤一收集所有写入事件。第二步解析动态值。步骤三与外部系统协调。在需要重放日志的情况下,只重放第三步。跳过步骤1和步骤2。这将如何实施取决于具体的制度。

猜你喜欢

转载自blog.csdn.net/lanyage_csdn/article/details/80642301