searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享

事件驱动编程

2024-07-15 09:44:44
8
0

事件驱动编程

我们生活的世界是由事件驱动的。我们每天都要对刺激做出反应并采取行动。事件驱动架构可让我们对应用程序进行建模,以密切反映真实世界的行为。

事件是捕捉系统状态变更的离散事件。这些事件由事件源产生,涵盖了从用户界面到物联网设备、无服务器函数等广泛的范畴。事件驱动编程是一种程序执行受此类事件支配的范式,而非遵循严格的顺序操作序列。

事件驱动编程促进了更加响应迅速和可扩展系统的开发。通过即时对发生的事件做出反应,应用程序能保持对用户交互的高度响应性,并能动态适应变化的工作负载。

对于大规模实时用户体验而言,响应能力和可扩展性是至关重要的考虑因素。事件驱动编程无缝贴合了这些要求。本文深入探讨了事件驱动编程的细节,着重强调了最佳实践和实施策略。

几个事件驱动编程概念

Concept Description
Events 系统内发生的离散事件或现象
Event-driven programming 编写捕捉、处理和响应异步事件的程序
Event-driven architecture 生产者生成事件,消费者订阅事件,并在事件发生时采取行动。事件代理/队列在生产者和消费者之间进行调解,保证可靠的事件交付。
Event processing 无状态处理将每个事件视为孤立发生的事件。在有状态处理过程中,系统会随着时间的推移维护事件上下文的相关信息。
Event persistence(事件持久性) 在数据库或其他存储机制中存储事件的过程
Event retrieval(事件检索) 从存储中检索事件进行处理的过程
Key considerations

- Consistency 一致性

- Atomicity 原子性

- Schemas 模式

- Event catalog 事件目录

 

什么是事件驱动架构?

事件驱动架构(EDA)是一种利用事件作为不同系统组件之间主要通信模式的架构模式。在传统架构中,组件之间会明确请求数据或服务,从而导致系统耦合更紧密。与此相反,事件驱动架构将组件解耦,使系统更具弹性和可扩展性。

架构的主要组成

  • 生产者的任务是生成事件并将其推送到系统中。这些实体可能包括用户界面、物联网设备或后台服务。
  • 消费者订阅事件并对其做出响应。这些消费者可以是单个服务、微服务或任何其他需要对特定事件做出响应的系统组件。
  • 事件存储是持久存储事件的地方。它们是系统状态的可靠真实来源。

生产者发布事件,由感兴趣的消费者订阅

图1-1:生产者发布事件,由感兴趣的消费者订阅

制作和消费活动

生产者-消费者模式在事件驱动程序设计中发挥着重要作用。生产者生成事件,消费者订阅事件,并在事件发生时采取行动。事件代理/队列在生产者和消费者之间进行调解,保证可靠的事件交付。

图1-2:生产者-消费者架构

在生产和消费事件时,我们可以将事件提炼为两种主要选择。

  • 在推式交互中,一旦事件发生,生产者就会主动向消费者发送事件。
  • 在拉动式交互中,消费者定期从集中式事件代理机构查询或拉动事件。

推式方法非常适合需要立即采取行动的实时应用。拉动式方法更适合实时性要求不那么严格的系统。

图1-3:推式和拉式的事件发布

实施方法

在事件驱动架构(EDA)的基础上,让我们来探讨有效实施和利用这种模式的各种方法。请记住,方法的选择取决于相关系统的独特要求、可扩展性考虑因素和架构偏好。

事件溯源

事件溯源是一种强有力的架构模式,它通过捕捉一系列不可变的事件来作为系统状态变化的记录,从而提供了一个全面的方法来管理和存储系统的状态。在信息技术领域,特别是提到“事件溯源(Event Sourcing)”时,它是一种软件设计模式,侧重于记录系统状态变化的完整历史,即每一状态变化都被视为一个不可变的事件并被存储起来。这一系列事件形成了系统行为的完整“时间轴”,允许我们追溯(即“溯源”)到过去的任意状态,或者分析系统演变的全过程。“溯源”核心在于通过连续、详尽的记录保持透明度和可追踪性,它是提高系统透明度、加强责任认定、优化流程管理、保障安全与质量的重要手段。处处体现了人造科学的影子。

在事件溯源中,事件代表系统内部的状态转换。它们不仅仅是通知,而是系统状态变更的权威记录。每个事件封装了一个特定的状态转换,捕获了事件在系统中发生的事情。一旦记录生成,这些事件就是不可变的,形成了一条不可更改的日志,作为所有状态变更的历史记录。

生产者是负责生成和发出事件的实体。而事件溯源中的消费者则是订阅并处理这些事件的实体。它们根据接收到的事件更新自己的状态或触发进一步的动作。

例如,生产者可以是一个应用程序组件,它接收用户命令并生成反映用户请求变更的事件。这个生产者根据接收到的动作启动事件的生成。

事件溯源通过提供一个可靠且有序的状态变更记录来帮助维持数据一致性。由于每个事件都是不可变的,系统确保了事件能够被忠实地记录下来,并且可以重放以重现任何给定时刻的确切状态。

命令溯源

命令溯源(Command Sourcing)是一种软件架构方法,其中生产者负责生成并发布命令。命令是表达改变系统状态的意图或请求。这些命令是基于用户操作或外部刺激响应生成的。

这些命令经过处理后会产生相应的事件。与事件溯源不同,后者中事件用来记录状态变更,命令溯源中的事件是因为命令处理过程而产生的,它们体现了系统状态的实际变更。

举个例子,当用户与一个网页应用交互,比如选择更新个人资料时,这一操作可能会触发一个更新用户资料的命令生成。

命令溯源的关键要素包括:

  1. 命令(Commands):命令是明确表达对系统进行某种改变的意向或请求。它们通常源于用户的直接操作(如点击按钮)、外部系统的消息或定时任务等。例如,“更新用户资料”就是一个命令,它表达了想要改变用户资料状态的意图。
  2. 命令生产者(Producers):负责生成和发出这些命令的组件。在应用中,这可能是用户界面背后的控制器,它监听用户交互并将这些交互转化为具体的命令对象。
  3. 命令处理器(Handlers/Processors):接收并处理命令的组件。处理器负责解析命令的内容,并据此执行必要的业务逻辑,这可能包括验证命令的有效性、更新系统状态等。与事件溯源不同,在命令溯源中,处理命令的过程会直接或间接导致状态变化,并可能产生相应的事件来记录这些变化。
  4. 事件生成:命令处理的结果可能会触发事件的产生。这些事件描述了系统状态的实际变化,是命令执行效果的体现。虽然命令本身表达了意图,但最终系统状态的改变以事件的形式被捕获和存储,这为系统状态的后续查询、审计和恢复提供了依据。

CQRS 命令查询职责分离

CQRS(命令查询职责分离)是一种重要的架构模式,它将系统中处理命令(写操作)和查询(读操作)的关注点分开。为了更清晰地说明,我们可以使用生产者和消费者的术语来探讨CQRS。

在CQRS的背景下,生产者生成命令。命令代表着修改系统状态的意图,并负责启动变更。生产者通常与命令端关联,是启动动作的实体,如用户界面、服务或外部系统。

CQRS的命令端负责验证命令、执行业务规则,并启动修改系统状态的适当操作。生产者生成命令,然后系统处理这些命令以产生反映状态变更的事件。同时,查询端专注于从数据存储中高效检索信息,用于展示或读取操作,与命令端分离,可以独立优化,从而提高系统的可伸缩性和性能。

图1-4:CQRS架构模式

命令在被成功处理后,会产生事件。这些事件代表了命令执行的结果,并封装了对系统所做的更改。这些事件随后被存储起来,成为系统状态的真相来源,这一过程对事件溯源至关重要。

在CQRS中,消费者订阅这些事件并负责更新他们的读模型或投影。这些消费者通常与查询端相关联,他们通过处理事件来维护一个去规范化且优化的数据视图,以便于高效查询。消费者包括诸如报告服务、分析引擎或任何需要读取系统状态的实体。

CQRS还引入了读模型的概念,这是为查询而优化的数据表示形式。投影是消费者用来根据命令端产生的事件更新和维护这些读模型的机制。读模型旨在满足消费者的特定需求,从而提升查询性能。简而言之,CQRS通过分离命令(修改数据的操作)和查询(读取数据的操作),并利用事件来驱动数据变化的记录与传播,以及通过读模型提供高效的查询访问,实现了复杂系统中的职责清晰和性能优化。

事件处理

在事件驱动架构的背景下,事件不仅仅充当简单的触发器;它们封装了关于系统状态变化和动态的宝贵细节。事件处理进一步深化了这一概念,通过集中对事件进行全面分析、有目的解释利用,以提取洞察并进行响应。事件处理可以根据处理方式分为有状态(stateful)和无状态(stateless)两种方法。像极了我们人类的认知活动,实际上在事件处理过程和人类认知活动过程中有许多相似的地方,我们从识别事件、提取事件特征、解释事件、处理事件这几个角度进行简单的对比。

  1. 识别事件
    • 人类认知:我们的感官(视觉、听觉、触觉等)不断接收外界信息,大脑会识别这些信息中的关键元素,比如声音、图像或触感,判断它们是否构成有意义的事件。比如,听到门铃声,大脑会识别这是一个访客到来的信号。
    • 事件驱动系统:系统通过传感器、API调用、用户输入等途径收集数据,然后根据预设的规则或模式识别出这些数据中的“事件”。例如,一个系统检测到数据库更新事件,表明数据状态已发生变化。
  2. 提取事件特征
    • 人类认知:一旦识别出事件,大脑会进一步分析该事件的特征,比如事件的来源、性质、重要程度等。继续以门铃声为例,我们可能还会注意到门铃的响度、持续时间和音调,这些特征有助于我们更精确地理解情境。
    • 事件处理:系统会对识别出的事件进行解析,提取关键属性或元数据,如事件的时间戳、来源、类型等。这些特征是后续处理和决策的基础。
  3. 解释事件
    • 人类认知:基于事件及其特征,我们会在脑海中构建意义,理解事件的含义和可能的后果。比如,门铃响起可能意味着快递到了,或是朋友来访。
    • 事件处理:系统通过算法或逻辑判断对事件进行解释,评估其对系统状态的影响或所需采取的行动。这可能涉及到模式匹配、规则引擎或机器学习模型来推断事件的意义。
  4. 对事件做出反馈
    • 人类认知:理解事件后,我们会根据情境和既定的目标、规则或情感反应做出决策,进而采取行动。比如,我们可能会去开门迎接访客。
    • 事件驱动系统:系统根据事件的解释结果,触发预设的处理逻辑或规则,执行相应的操作。这可能包括更新数据库记录、发送通知、调整资源分配等。

通过这种对比可以看出,尽管机制不同,但事件驱动系统的设计灵感在很大程度上来源于我们对人类认知过程的理解,旨在让计算机系统能够更加智能、灵活地响应复杂多变的环境。

无状态事件处理

无状态处理是指事件处理器在处理每个事件时不需要依赖或记住之前的事件信息,每个事件被独立处理。这种方式简化了系统设计,提高了处理速度,适合于那些事件间相互独立,无需维护长期状态的应用场景,如实时日志分析、简单的通知服务等。

// 从指定主机名和端口的套接字连接中读取文本行,创建 DStream(离散流)。
val lines_read = sc.socketTextStream("localhost", 8888)

// 通过将每行分割成字来转换输入内容,然后将每个字转换成整数
val numbers_read = lines_read.flatMap(_.split(" ")).map(_.toInt) 

// 计算整数的总和
val total_sum = numbers_read.reduce(_ + _) 

有状态事件处理

与之相对,在这种处理模式下,处理引擎会维护事件处理的中间状态或上下文信息。这意味着,处理某个事件的结果会影响到后续事件的处理方式。有状态处理适用于那些需要根据过往事件序列做决策的场景,比如复杂的业务流程、聚合计算或是维持会话状态。

// 从指定主机名和端口的套接字连接中读取文本行,建立 DStream。
val lines_read = sc.socketTextStream("localhost", 8888) 

// 将输入的 DStream 行分割成字,并将每个字转换成整数。
val numbers_read = lines_read.flatMap(_.split(" ")).map(_.toInt)

// 对 DStream 应用滑动窗口操作,创建一个新的 DStream (window_op),每隔 5 秒捕获最后 10 秒的数据。
val window_op = numbers.window(Seconds(10), Seconds(5)) 

// 将每个元素映射为一个元组(1,n),并计算指定窗口内元素的总和与计数。
val totalSum = window_op.map(n => (1, n)).reduceByKey((a, b) => (a._1 + b._1, a._2 + b._2)) 

// 取平均值
val average = totalSum.mapValues(sum => sum._2 / sum._1) 

无论是哪种处理方式,事件处理的核心价值在于能够实时或近乎实时地对系统中的状态变化做出响应,从而实现自动化决策、优化业务流程、增强系统弹性和智能化水平。

事件存储和事件持久化

事件持久化指的是将事件存储在数据库或其他存储机制中,而事件检索指的是从存储中检索事件以进行处理。对事件驱动系统的性能来说,高效地存储和检索事件的机制至关重要。实施正确的策略可以确保在不损害系统响应性的情况下,当需要时事件是可访问的。

事件可以以多种方式存储,例如在关系型数据库、NoSQL数据库或者消息队列中。选择存储方式取决于系统的具体需求,如性能、可扩展性和数据一致性。你可能需要考虑以下因素:

  • 事件量
  • 所需的查询能力
  • 可用性要求

你可以执行事件索引以支持事件的有效查询和过滤。这涉及在事件字段上创建索引,如时间、来源或类型,以提高检索性能。

事件驱动编程的基本考虑因素

在实现事件驱动的工作流程时,请考虑以下事项:

一致性和并发性

一致性和并发性是事件驱动系统中两个重要的考虑因素。实现和维护这些特性对于系统的可靠性和准确性至关重要。一致性确保系统的当前状态准确地反映了事件的顺序,而并发性则解决处理多个可能同时进行的操作的挑战。

单一写入者原则是一个基本概念,它确保了一致性和数据完整性。它规定只允许一个写入者向给定的事件日志写入。这确保了事件被一致地处理,没有冲突或不一致。

您可以使用锁或闩来同步事件访问并实现单一写入者原则。这确保了只有一个写入者可以同时访问一个事件,防止冲突并确保一致性。

版本控制

生成事件时,应该为它们分配一个唯一的版本号。这允许消费者按非顺序处理事件,并确保事件按正确的顺序进行处理。

幂等性

幂等性意味着一个事件可以被多次处理,而不改变系统的当前状态。这个属性确保了如果事件失败,可以安全地重试,简化了事件的处理。

冲突解决

冲突解决机制解决当多个事件同时处理时出现的冲突。这些机制可以包括版本控制、仲裁或调和等技术。

事务的原子性

在事件驱动系统中,保持事务的原子性是一个基本要求,特别是在同时处理多个事件时。在微服务架构中,这一点尤为重要,因为每个微服务的自治性和独立性是整个架构的关键方面。

定义清晰的界限

在这样的环境中,要有效地实现事务内的原子性,必须为每个微服务划定清晰的界限。这些界限定义了所有权和数据修改权限,确保每个微服务独立运行,并且事务被精心管理。通过明确指定微服务拥有和可以修改的数据,这种方法防止了在事件处理过程中发生意外的冲突。

使用事务框架

事务框架管理跨多个微服务的事务,确保所有事件得到适当的协调,系统保持一致性。流行的事务框架包括Saga、Axon和Spring Cloud Transacted。

管理数据演变的模式

使用模式来管理数据随时间的演变是事件驱动系统的一个重要方面。模式为数据提供了结构,可以用于确保数据在演变过程中的一致性和完整性。

为你系统中的每个数据实体定义一个模式,包括字段及其数据类型。这样,包含特定实体数据的所有事件都符合相同的结构,使得数据处理和分析更加容易。

定义一个模式版本控制策略,包括每个模式的版本号或标识符。在对模式进行更改时,确保新版本与以前的版本向后兼容。向后兼容性确保使用旧版本模式处理的事件能够被正确处理。即使引入了新模式版本,系统也保持一致状态。

事件目录

事件目录是事件驱动系统中发生的所有事件的集中存储库。它为不同系统组件之间交换的事件提供了共同的语言和理解。

事件目录应该有清晰的结构,便于导航和理解。你可以按类型对事件进行分类,例如用户事件、系统事件或业务事件。

记录事件之间的关系,例如哪些事件触发了其他事件,或者哪些事件是由其他事件触发的。这有助于开发者和业务利益相关者理解事件是如何连接的,以及它们如何适应整个系统架构。

结论

总之,事件驱动编程不仅仅是一个时髦的词汇,而是现代软件开发中的一个变革性范式。通过利用事件的力量,系统可以实现增强的响应性、可扩展性和适应性。

本文以技术为重点深入探讨了事件驱动编程,强调了最佳实践和实施策略。作为工程师,了解并采纳这些实践对于满足当今软件开发格局不断演变的需求至关重要。

Command Query Responsibility Segregation (CQRS)—simplified

0条评论
0 / 1000
木喳喳
9文章数
1粉丝数
木喳喳
9 文章 | 1 粉丝

事件驱动编程

2024-07-15 09:44:44
8
0

事件驱动编程

我们生活的世界是由事件驱动的。我们每天都要对刺激做出反应并采取行动。事件驱动架构可让我们对应用程序进行建模,以密切反映真实世界的行为。

事件是捕捉系统状态变更的离散事件。这些事件由事件源产生,涵盖了从用户界面到物联网设备、无服务器函数等广泛的范畴。事件驱动编程是一种程序执行受此类事件支配的范式,而非遵循严格的顺序操作序列。

事件驱动编程促进了更加响应迅速和可扩展系统的开发。通过即时对发生的事件做出反应,应用程序能保持对用户交互的高度响应性,并能动态适应变化的工作负载。

对于大规模实时用户体验而言,响应能力和可扩展性是至关重要的考虑因素。事件驱动编程无缝贴合了这些要求。本文深入探讨了事件驱动编程的细节,着重强调了最佳实践和实施策略。

几个事件驱动编程概念

Concept Description
Events 系统内发生的离散事件或现象
Event-driven programming 编写捕捉、处理和响应异步事件的程序
Event-driven architecture 生产者生成事件,消费者订阅事件,并在事件发生时采取行动。事件代理/队列在生产者和消费者之间进行调解,保证可靠的事件交付。
Event processing 无状态处理将每个事件视为孤立发生的事件。在有状态处理过程中,系统会随着时间的推移维护事件上下文的相关信息。
Event persistence(事件持久性) 在数据库或其他存储机制中存储事件的过程
Event retrieval(事件检索) 从存储中检索事件进行处理的过程
Key considerations

- Consistency 一致性

- Atomicity 原子性

- Schemas 模式

- Event catalog 事件目录

 

什么是事件驱动架构?

事件驱动架构(EDA)是一种利用事件作为不同系统组件之间主要通信模式的架构模式。在传统架构中,组件之间会明确请求数据或服务,从而导致系统耦合更紧密。与此相反,事件驱动架构将组件解耦,使系统更具弹性和可扩展性。

架构的主要组成

  • 生产者的任务是生成事件并将其推送到系统中。这些实体可能包括用户界面、物联网设备或后台服务。
  • 消费者订阅事件并对其做出响应。这些消费者可以是单个服务、微服务或任何其他需要对特定事件做出响应的系统组件。
  • 事件存储是持久存储事件的地方。它们是系统状态的可靠真实来源。

生产者发布事件,由感兴趣的消费者订阅

图1-1:生产者发布事件,由感兴趣的消费者订阅

制作和消费活动

生产者-消费者模式在事件驱动程序设计中发挥着重要作用。生产者生成事件,消费者订阅事件,并在事件发生时采取行动。事件代理/队列在生产者和消费者之间进行调解,保证可靠的事件交付。

图1-2:生产者-消费者架构

在生产和消费事件时,我们可以将事件提炼为两种主要选择。

  • 在推式交互中,一旦事件发生,生产者就会主动向消费者发送事件。
  • 在拉动式交互中,消费者定期从集中式事件代理机构查询或拉动事件。

推式方法非常适合需要立即采取行动的实时应用。拉动式方法更适合实时性要求不那么严格的系统。

图1-3:推式和拉式的事件发布

实施方法

在事件驱动架构(EDA)的基础上,让我们来探讨有效实施和利用这种模式的各种方法。请记住,方法的选择取决于相关系统的独特要求、可扩展性考虑因素和架构偏好。

事件溯源

事件溯源是一种强有力的架构模式,它通过捕捉一系列不可变的事件来作为系统状态变化的记录,从而提供了一个全面的方法来管理和存储系统的状态。在信息技术领域,特别是提到“事件溯源(Event Sourcing)”时,它是一种软件设计模式,侧重于记录系统状态变化的完整历史,即每一状态变化都被视为一个不可变的事件并被存储起来。这一系列事件形成了系统行为的完整“时间轴”,允许我们追溯(即“溯源”)到过去的任意状态,或者分析系统演变的全过程。“溯源”核心在于通过连续、详尽的记录保持透明度和可追踪性,它是提高系统透明度、加强责任认定、优化流程管理、保障安全与质量的重要手段。处处体现了人造科学的影子。

在事件溯源中,事件代表系统内部的状态转换。它们不仅仅是通知,而是系统状态变更的权威记录。每个事件封装了一个特定的状态转换,捕获了事件在系统中发生的事情。一旦记录生成,这些事件就是不可变的,形成了一条不可更改的日志,作为所有状态变更的历史记录。

生产者是负责生成和发出事件的实体。而事件溯源中的消费者则是订阅并处理这些事件的实体。它们根据接收到的事件更新自己的状态或触发进一步的动作。

例如,生产者可以是一个应用程序组件,它接收用户命令并生成反映用户请求变更的事件。这个生产者根据接收到的动作启动事件的生成。

事件溯源通过提供一个可靠且有序的状态变更记录来帮助维持数据一致性。由于每个事件都是不可变的,系统确保了事件能够被忠实地记录下来,并且可以重放以重现任何给定时刻的确切状态。

命令溯源

命令溯源(Command Sourcing)是一种软件架构方法,其中生产者负责生成并发布命令。命令是表达改变系统状态的意图或请求。这些命令是基于用户操作或外部刺激响应生成的。

这些命令经过处理后会产生相应的事件。与事件溯源不同,后者中事件用来记录状态变更,命令溯源中的事件是因为命令处理过程而产生的,它们体现了系统状态的实际变更。

举个例子,当用户与一个网页应用交互,比如选择更新个人资料时,这一操作可能会触发一个更新用户资料的命令生成。

命令溯源的关键要素包括:

  1. 命令(Commands):命令是明确表达对系统进行某种改变的意向或请求。它们通常源于用户的直接操作(如点击按钮)、外部系统的消息或定时任务等。例如,“更新用户资料”就是一个命令,它表达了想要改变用户资料状态的意图。
  2. 命令生产者(Producers):负责生成和发出这些命令的组件。在应用中,这可能是用户界面背后的控制器,它监听用户交互并将这些交互转化为具体的命令对象。
  3. 命令处理器(Handlers/Processors):接收并处理命令的组件。处理器负责解析命令的内容,并据此执行必要的业务逻辑,这可能包括验证命令的有效性、更新系统状态等。与事件溯源不同,在命令溯源中,处理命令的过程会直接或间接导致状态变化,并可能产生相应的事件来记录这些变化。
  4. 事件生成:命令处理的结果可能会触发事件的产生。这些事件描述了系统状态的实际变化,是命令执行效果的体现。虽然命令本身表达了意图,但最终系统状态的改变以事件的形式被捕获和存储,这为系统状态的后续查询、审计和恢复提供了依据。

CQRS 命令查询职责分离

CQRS(命令查询职责分离)是一种重要的架构模式,它将系统中处理命令(写操作)和查询(读操作)的关注点分开。为了更清晰地说明,我们可以使用生产者和消费者的术语来探讨CQRS。

在CQRS的背景下,生产者生成命令。命令代表着修改系统状态的意图,并负责启动变更。生产者通常与命令端关联,是启动动作的实体,如用户界面、服务或外部系统。

CQRS的命令端负责验证命令、执行业务规则,并启动修改系统状态的适当操作。生产者生成命令,然后系统处理这些命令以产生反映状态变更的事件。同时,查询端专注于从数据存储中高效检索信息,用于展示或读取操作,与命令端分离,可以独立优化,从而提高系统的可伸缩性和性能。

图1-4:CQRS架构模式

命令在被成功处理后,会产生事件。这些事件代表了命令执行的结果,并封装了对系统所做的更改。这些事件随后被存储起来,成为系统状态的真相来源,这一过程对事件溯源至关重要。

在CQRS中,消费者订阅这些事件并负责更新他们的读模型或投影。这些消费者通常与查询端相关联,他们通过处理事件来维护一个去规范化且优化的数据视图,以便于高效查询。消费者包括诸如报告服务、分析引擎或任何需要读取系统状态的实体。

CQRS还引入了读模型的概念,这是为查询而优化的数据表示形式。投影是消费者用来根据命令端产生的事件更新和维护这些读模型的机制。读模型旨在满足消费者的特定需求,从而提升查询性能。简而言之,CQRS通过分离命令(修改数据的操作)和查询(读取数据的操作),并利用事件来驱动数据变化的记录与传播,以及通过读模型提供高效的查询访问,实现了复杂系统中的职责清晰和性能优化。

事件处理

在事件驱动架构的背景下,事件不仅仅充当简单的触发器;它们封装了关于系统状态变化和动态的宝贵细节。事件处理进一步深化了这一概念,通过集中对事件进行全面分析、有目的解释利用,以提取洞察并进行响应。事件处理可以根据处理方式分为有状态(stateful)和无状态(stateless)两种方法。像极了我们人类的认知活动,实际上在事件处理过程和人类认知活动过程中有许多相似的地方,我们从识别事件、提取事件特征、解释事件、处理事件这几个角度进行简单的对比。

  1. 识别事件
    • 人类认知:我们的感官(视觉、听觉、触觉等)不断接收外界信息,大脑会识别这些信息中的关键元素,比如声音、图像或触感,判断它们是否构成有意义的事件。比如,听到门铃声,大脑会识别这是一个访客到来的信号。
    • 事件驱动系统:系统通过传感器、API调用、用户输入等途径收集数据,然后根据预设的规则或模式识别出这些数据中的“事件”。例如,一个系统检测到数据库更新事件,表明数据状态已发生变化。
  2. 提取事件特征
    • 人类认知:一旦识别出事件,大脑会进一步分析该事件的特征,比如事件的来源、性质、重要程度等。继续以门铃声为例,我们可能还会注意到门铃的响度、持续时间和音调,这些特征有助于我们更精确地理解情境。
    • 事件处理:系统会对识别出的事件进行解析,提取关键属性或元数据,如事件的时间戳、来源、类型等。这些特征是后续处理和决策的基础。
  3. 解释事件
    • 人类认知:基于事件及其特征,我们会在脑海中构建意义,理解事件的含义和可能的后果。比如,门铃响起可能意味着快递到了,或是朋友来访。
    • 事件处理:系统通过算法或逻辑判断对事件进行解释,评估其对系统状态的影响或所需采取的行动。这可能涉及到模式匹配、规则引擎或机器学习模型来推断事件的意义。
  4. 对事件做出反馈
    • 人类认知:理解事件后,我们会根据情境和既定的目标、规则或情感反应做出决策,进而采取行动。比如,我们可能会去开门迎接访客。
    • 事件驱动系统:系统根据事件的解释结果,触发预设的处理逻辑或规则,执行相应的操作。这可能包括更新数据库记录、发送通知、调整资源分配等。

通过这种对比可以看出,尽管机制不同,但事件驱动系统的设计灵感在很大程度上来源于我们对人类认知过程的理解,旨在让计算机系统能够更加智能、灵活地响应复杂多变的环境。

无状态事件处理

无状态处理是指事件处理器在处理每个事件时不需要依赖或记住之前的事件信息,每个事件被独立处理。这种方式简化了系统设计,提高了处理速度,适合于那些事件间相互独立,无需维护长期状态的应用场景,如实时日志分析、简单的通知服务等。

// 从指定主机名和端口的套接字连接中读取文本行,创建 DStream(离散流)。
val lines_read = sc.socketTextStream("localhost", 8888)

// 通过将每行分割成字来转换输入内容,然后将每个字转换成整数
val numbers_read = lines_read.flatMap(_.split(" ")).map(_.toInt) 

// 计算整数的总和
val total_sum = numbers_read.reduce(_ + _) 

有状态事件处理

与之相对,在这种处理模式下,处理引擎会维护事件处理的中间状态或上下文信息。这意味着,处理某个事件的结果会影响到后续事件的处理方式。有状态处理适用于那些需要根据过往事件序列做决策的场景,比如复杂的业务流程、聚合计算或是维持会话状态。

// 从指定主机名和端口的套接字连接中读取文本行,建立 DStream。
val lines_read = sc.socketTextStream("localhost", 8888) 

// 将输入的 DStream 行分割成字,并将每个字转换成整数。
val numbers_read = lines_read.flatMap(_.split(" ")).map(_.toInt)

// 对 DStream 应用滑动窗口操作,创建一个新的 DStream (window_op),每隔 5 秒捕获最后 10 秒的数据。
val window_op = numbers.window(Seconds(10), Seconds(5)) 

// 将每个元素映射为一个元组(1,n),并计算指定窗口内元素的总和与计数。
val totalSum = window_op.map(n => (1, n)).reduceByKey((a, b) => (a._1 + b._1, a._2 + b._2)) 

// 取平均值
val average = totalSum.mapValues(sum => sum._2 / sum._1) 

无论是哪种处理方式,事件处理的核心价值在于能够实时或近乎实时地对系统中的状态变化做出响应,从而实现自动化决策、优化业务流程、增强系统弹性和智能化水平。

事件存储和事件持久化

事件持久化指的是将事件存储在数据库或其他存储机制中,而事件检索指的是从存储中检索事件以进行处理。对事件驱动系统的性能来说,高效地存储和检索事件的机制至关重要。实施正确的策略可以确保在不损害系统响应性的情况下,当需要时事件是可访问的。

事件可以以多种方式存储,例如在关系型数据库、NoSQL数据库或者消息队列中。选择存储方式取决于系统的具体需求,如性能、可扩展性和数据一致性。你可能需要考虑以下因素:

  • 事件量
  • 所需的查询能力
  • 可用性要求

你可以执行事件索引以支持事件的有效查询和过滤。这涉及在事件字段上创建索引,如时间、来源或类型,以提高检索性能。

事件驱动编程的基本考虑因素

在实现事件驱动的工作流程时,请考虑以下事项:

一致性和并发性

一致性和并发性是事件驱动系统中两个重要的考虑因素。实现和维护这些特性对于系统的可靠性和准确性至关重要。一致性确保系统的当前状态准确地反映了事件的顺序,而并发性则解决处理多个可能同时进行的操作的挑战。

单一写入者原则是一个基本概念,它确保了一致性和数据完整性。它规定只允许一个写入者向给定的事件日志写入。这确保了事件被一致地处理,没有冲突或不一致。

您可以使用锁或闩来同步事件访问并实现单一写入者原则。这确保了只有一个写入者可以同时访问一个事件,防止冲突并确保一致性。

版本控制

生成事件时,应该为它们分配一个唯一的版本号。这允许消费者按非顺序处理事件,并确保事件按正确的顺序进行处理。

幂等性

幂等性意味着一个事件可以被多次处理,而不改变系统的当前状态。这个属性确保了如果事件失败,可以安全地重试,简化了事件的处理。

冲突解决

冲突解决机制解决当多个事件同时处理时出现的冲突。这些机制可以包括版本控制、仲裁或调和等技术。

事务的原子性

在事件驱动系统中,保持事务的原子性是一个基本要求,特别是在同时处理多个事件时。在微服务架构中,这一点尤为重要,因为每个微服务的自治性和独立性是整个架构的关键方面。

定义清晰的界限

在这样的环境中,要有效地实现事务内的原子性,必须为每个微服务划定清晰的界限。这些界限定义了所有权和数据修改权限,确保每个微服务独立运行,并且事务被精心管理。通过明确指定微服务拥有和可以修改的数据,这种方法防止了在事件处理过程中发生意外的冲突。

使用事务框架

事务框架管理跨多个微服务的事务,确保所有事件得到适当的协调,系统保持一致性。流行的事务框架包括Saga、Axon和Spring Cloud Transacted。

管理数据演变的模式

使用模式来管理数据随时间的演变是事件驱动系统的一个重要方面。模式为数据提供了结构,可以用于确保数据在演变过程中的一致性和完整性。

为你系统中的每个数据实体定义一个模式,包括字段及其数据类型。这样,包含特定实体数据的所有事件都符合相同的结构,使得数据处理和分析更加容易。

定义一个模式版本控制策略,包括每个模式的版本号或标识符。在对模式进行更改时,确保新版本与以前的版本向后兼容。向后兼容性确保使用旧版本模式处理的事件能够被正确处理。即使引入了新模式版本,系统也保持一致状态。

事件目录

事件目录是事件驱动系统中发生的所有事件的集中存储库。它为不同系统组件之间交换的事件提供了共同的语言和理解。

事件目录应该有清晰的结构,便于导航和理解。你可以按类型对事件进行分类,例如用户事件、系统事件或业务事件。

记录事件之间的关系,例如哪些事件触发了其他事件,或者哪些事件是由其他事件触发的。这有助于开发者和业务利益相关者理解事件是如何连接的,以及它们如何适应整个系统架构。

结论

总之,事件驱动编程不仅仅是一个时髦的词汇,而是现代软件开发中的一个变革性范式。通过利用事件的力量,系统可以实现增强的响应性、可扩展性和适应性。

本文以技术为重点深入探讨了事件驱动编程,强调了最佳实践和实施策略。作为工程师,了解并采纳这些实践对于满足当今软件开发格局不断演变的需求至关重要。

Command Query Responsibility Segregation (CQRS)—simplified

文章来自个人专栏
现代操作系统原理及实现
9 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0