事件驱动的架构样式

Azure 流分析
Azure Functions
Azure 服务总线

事件驱动的体系结构由生成事件流、侦听这些事件的事件使用者以及将事件从生成者传输到使用者的事件通道组成

显示事件驱动的体系结构样式的关系图。

事件可几乎实时发送,因此使用者可在事件发生时立即做出响应。 生成者与使用者分离:生成者不知道哪些使用者正在侦听。 使用者之间也能彼此脱离,且每个使用者都能看到所有事件。 此过程不同于 竞争使用者模式 ,即使用者从队列拉取消息,并且仅处理一次消息,假设没有错误。 在某些系统(如 Azure 物联网(IoT)中,事件必须大量引入。

事件驱动的体系结构可以使用 发布-订阅模型 或事件流模型。

  • 发布/订阅: 发布-订阅消息传送基础结构跟踪订阅。 事件发布后,它会将事件发送给每位订阅者。 收到事件后无法重播事件,并且新订阅者看不到该事件。

  • 事件流式处理: 事件将写入日志。 事件严格按分区顺序排序,并且是持久事件。 客户端不订阅流。 相反,客户端可以从流的任何部分读取。 客户端负责在流中提升其位置。 这意味着客户端可以随时加入,并且可以重播事件。

在使用者端,有一些常见的变化:

  • 简单事件处理: 事件会立即触发使用者中的作。 例如,可以将 Azure Functions 与 Azure 服务总线触发器配合使用,以便每当将消息发布到服务总线主题时,函数就会运行。

  • 基本事件关联: 使用者处理一些离散的业务事件,将它们与某些标识符相关联,并保留早期事件中的信息,以便在处理后续事件时使用。 NServiceBusMassTransit 等库支持此模式。

  • 复杂事件处理: 使用者使用 Azure 流分析等技术来分析一系列事件并识别事件数据中的模式。 例如,可以在时间范围内聚合嵌入设备的读取,并在移动平均值超过特定阈值时生成通知。

  • 事件流处理: 使用数据流式处理平台(例如 Azure IoT 中心或 Apache Kafka)作为管道来引入事件并将其馈送给流处理器。 此流处理器可处理或转换流。 应用程序的不同子系统可能有多个流处理器。 此方法非常适合 IoT 工作负荷。

事件源可能位于系统外部,例如 IoT 解决方案中的物理设备。 在这种情况下,系统必须能够将数据引入数据源所需的卷和吞吐量。

有两种构建事件有效负载的主要方法。 在控制事件使用者时,可以决定每个使用者的有效负载结构。 通过此策略,可以在单个工作负荷中根据需要混合使用方法。

  • 在有效负载中包含所有必需的属性: 如果希望使用者拥有所有可用信息,而无需查询外部数据源,请使用此方法。 但是,由于多个 记录系统(尤其是在更新后)导致数据一致性问题。 合同管理和版本控制也可能变得复杂。

  • 仅将密钥包含在有效负载中: 在此方法中,使用者检索必要的属性(如主键),以便从数据源中独立提取剩余数据。 此方法提供更好的数据一致性,因为它具有单个记录系统。 但是,它的性能可能低于第一种方法,因为使用者必须经常查询数据源。 由于较小的事件和更简单的合同降低了对耦合、带宽、合同管理或版本控制的担忧。

在上图中,每种类型的使用者都显示为单个框。 为了避免使用者成为系统中的单一故障点,通常具有使用者的多个实例。 可能还需要多个实例来处理事件的音量和频率。 单个使用者可以处理多个线程上的事件。 如果必须按顺序处理事件或需要完全一次的语义,则此设置可能会引发质询。 有关详细信息,请参阅 最小化协调

许多事件驱动体系结构中有两个主要拓扑:

  • 中转站拓扑: 组件将事件广播为整个系统的事件。 其他组件要么对事件执行作,要么忽略该事件。 当事件处理流相对简单时,此拓扑非常有用。 没有集中协调或业务流程,因此此拓扑可以是动态的。 此拓扑高度分离,这有助于提供可伸缩性、响应能力和组件容错能力。 没有任何组件拥有或了解任何多步骤业务交易的状态,并且操作是异步执行的。 随后,分布式事务存在风险,因为无法以本机方式重启或重播。 需要仔细考虑错误处理和手动干预策略,因为此拓扑可能是数据源不一致。

  • 中介拓扑: 此拓扑解决了中转站拓扑的一些缺点。 有一个事件调解程序可以管理和控制事件流。 事件中介器维护状态并管理错误处理和重启功能。 与此代理拓扑形成鲜明对比的是,在此拓扑中,组件以命令的形式广播事件,并且仅广播到指定的通道。 这些通道通常是消息队列。 使用者应处理这些命令。 此拓扑提供更多的控制、更好的分布式错误处理以及潜在的更好的数据一致性。 此拓扑确实引入了组件之间的增加耦合,事件中介可能会成为瓶颈或可靠性问题。

何时使用此架构

应在以下情况下使用此体系结构:

  • 多个子系统必须处理相同的事件。
  • 需要具有最短时间延迟的实时处理。
  • 需要复杂的事件处理,例如随时间窗口的模式匹配或聚合。
  • 需要大量数据和高速数据,例如 IoT。

好处

此体系结构的优点包括:

  • 生成者和使用者相脱离。
  • 没有点到点的集成。 容易向系统添加新使用者。
  • 使用者可以在事件发生时立即响应事件。
  • 高度可缩放、弹性和分布式。
  • 子系统具有独立的事件流视图。

挑战

  • 有保证的传递。

    在某些系统中,尤其是在 IoT 方案中,保证数据传递至关重要。

  • 按顺序或者“恰一次”处理事件。

    为了获得复原能力和可伸缩性,每个使用者类型通常在多个实例中运行。 如果事件必须在使用者类型内按顺序处理,或者未实现 幂等消息处理 逻辑,则此过程可能会创建质询。

  • 跨服务的消息协调。

    业务流程通常有多个服务发布和订阅消息,以实现整个工作负荷的一致结果。 可以使用 工作流模式 (如 编舞模式Saga Orchestration )可靠地跨各种服务管理消息流。

  • 错误处理。

    事件驱动的体系结构主要使用异步通信。 异步通信的一个挑战是错误处理。 解决此问题的一种方法是使用单独的错误处理程序处理器。 当事件使用者遇到错误时,它会立即并异步地将错误事件发送到错误处理程序处理器,然后继续作。 错误处理程序处理器尝试修复错误,并将事件发送回原始引入通道。 但是,如果错误处理程序处理器失败,它可以将错误事件发送到管理员以进一步检查。 如果使用错误处理程序处理器,则重新提交错误事件时会按顺序处理错误事件。

  • 数据丢失。

    异步通信的另一个挑战是数据丢失。 如果任何组件在成功处理之前崩溃,并将事件移交给其下一个组件,则会删除该事件,并且永远不会使其进入最终目标。 为了最大程度地减少数据丢失的可能性,请保留传输中的事件,并仅在下一个组件确认收到事件时删除或取消排队事件。 这些功能称为 客户端确认模式最后一个参与者支持

  • 实现传统的请求-响应模式。

    有时,事件生成者需要事件使用者的即时响应,例如在继续订单之前获取客户资格。 在事件驱动的体系结构中,可以使用 请求-响应消息传送实现同步通信。

    此模式通常通过两个队列实现:请求队列和响应队列。 事件生成者向请求队列发送异步请求,暂停该任务的其他作,并在回复队列中等待响应。 此方法有效地将此模式转换为同步过程。 然后,事件使用者处理请求并通过响应队列发送回复。 此方法通常使用会话 ID 进行跟踪,因此事件生成者知道响应队列中的哪个消息与特定请求相关。 原始请求还可以在 回复标头 或其他相互同意的自定义属性中指定响应队列的名称(可能是临时的)。

  • 维护适当的事件数。

    生成过多的细粒度事件会使系统饱和并压倒系统,从而难以有效地分析事件的整体流。 需要回滚更改时,此问题会加剧。 相反,过度合并事件也会造成问题,导致事件使用者不必要的处理和响应。

    若要实现正确的平衡,请考虑事件的后果以及使用者是否需要检查事件有效负载以确定其响应。 例如,如果你有符合性检查组件,可能只发布两种类型的事件: 合规不符合。 此方法有助于确保仅相关使用者处理每个事件,从而防止不必要的处理。

其他注意事项

  • 要包含在事件中的数据量可能是影响性能和成本的重要考虑因素。 可以通过在事件中直接放置处理所需的所有相关信息来简化处理代码并消除额外的查找。 仅向事件添加少量信息(例如几个标识符)时,可以缩短传输时间和成本。 但是,此方法要求处理代码检索它所需的任何其他信息。 有关详细信息,请参阅 将事件放在饮食上

  • 请求仅对请求处理组件可见。 但事件通常对工作负荷中的多个组件可见,即使这些组件不使用这些组件,也不打算使用这些组件。 若要以“假设违规”思维模式进行作,请注意事件中包含的信息,以防止意外的信息泄露。

  • 许多应用程序使用事件驱动的体系结构作为其主要体系结构。 可以将此方法与其他体系结构样式相结合,以创建混合体系结构。 典型的组合包括 微服务管道和筛选器。 集成事件驱动的体系结构,通过消除瓶颈并在高请求量期间提供 后台压力 来增强系统性能。

  • 特定域 通常跨越多个事件生成者、使用者或事件通道。 对特定域的更改可能会影响许多组件。

  • 社区讨论视频,介绍在协调和业务流程之间进行选择时需考虑的事项。