你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

事件驱动的体系结构样式

事件驱动的体系结构由生成事件流、侦听这些事件的事件使用者以及事件通道(通常实现为事件代理或引入服务)的事件生成者组成,这些事件将事件从生成者传输到使用者。

Architecture

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

事件几乎实时传送,因此使用者可以在事件发生时立即响应事件。 生成者与使用者分离,这意味着生成者不知道哪些使用者正在侦听。 使用者也相互分离,每个使用者都会看到所有事件。

此过程不同于 竞争使用者模式。 在竞争使用者模式中,使用者从队列中提取消息。 每个消息只处理一次,假设没有错误。 在某些系统(如 Azure IoT)中,事件必须引入到大量位置。

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

  • 发布-订阅: 发布-订阅消息传送基础结构跟踪订阅。 发布事件后,它会将事件发送到每个订阅服务器。 传递事件后,无法重播该事件,并且新订阅者看不到该事件。 建议将 Azure 事件网格 用于发布-订阅方案。

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

在使用者方面,有一些常见的变体:

  • 简单事件处理: 事件会立即触发使用者中的作。 例如,可以将 Azure Functions事件网格触发器Azure 服务总线触发器 配合使用,以便在发布消息时运行代码。

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

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

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

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

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

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

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

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

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

  • 中转站拓扑: 组件将事件广播到整个系统。 其他组件要么对事件执行作,要么忽略该事件。 当事件处理流相对简单时,此拓扑非常有用。 没有集中协调或业务流程,因此此拓扑可以是动态的。

    此拓扑高度分离,有助于提供可伸缩性、响应能力和组件容错能力。 没有任何组件拥有或知道任何多步骤业务事务的状态,并且会异步执行作。 因此,分布式事务存在风险,因为没有用于重启或重播它们的内置机制。 需要仔细考虑错误处理和手动干预策略,因为此拓扑可能是数据源不一致。

  • 中介拓扑: 此拓扑解决了中转站拓扑的一些缺点。 有一个事件调解程序可以管理和控制事件流。 事件调解程序维护状态并管理错误处理和重启功能。 与中转站拓扑不同,中介拓扑中的组件以命令的形式广播,并且仅广播到指定的通道。 这些通道通常是消息队列。 使用者应处理这些命令。

    此拓扑提供更多的控制、更好的分布式错误处理以及潜在的更好的数据一致性。 但是,此拓扑引入了组件之间的增加耦合,事件调解器可能会成为瓶颈或可靠性问题。

何时使用此体系结构

如果满足以下条件,则应使用此体系结构:

  • 多个子系统必须处理相同的事件。

  • 需要具有最短时间延迟的实时处理。

  • 需要复杂的事件处理,例如随时间窗口的模式匹配或聚合。

  • 需要大量数据和高速数据,例如使用 IoT。

  • 你需要将生成者和使用者分离,实现独立的可伸缩性和可靠性目标。

优点

此体系结构具有以下优势:

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

挑战

  • 有保证的交付

    在某些系统中,特别是在 IoT 方案中,确保事件传送至关重要。

  • 按顺序处理事件或只处理一次事件

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

  • 跨服务的消息协调

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

  • 错误处理

    事件驱动的体系结构主要依赖于异步通信。 异步通信存在的常见挑战是错误处理。 解决此问题的一种方法是使用专用错误处理程序处理器。

    当事件使用者遇到错误时,它会立即并异步地将有问题的事件发送到错误处理程序处理器,并继续处理其他事件。 错误处理程序处理器尝试解决问题。 如果成功,错误处理程序处理器会将事件重新提交到原始引入通道。 如果失败,处理器可以将事件转发给管理员,以便进行进一步检查。 使用错误处理程序处理器时,将按顺序处理重新提交的事件。

  • 数据丢失

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

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

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

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

  • 维护适当的事件数

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

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

其他注意事项

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

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

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

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

后续步骤