6 种事件驱动的架构模式

事件驱动架构(Event-Driven Architecture)是一种基于事件和事件处理的软件架构,它的核心思想是将系统的行为和逻辑抽象成一系列事件,这些事件在系统中按照一定的规则和顺序产生和传播,并被相应的处理器处理。事件驱动架构具有高度的灵活性、可扩展性和可维护性,因此在现代软件系统中广泛应用。

事件驱动架构模式是常见的分布式异步架构模式,通常被用来创建具备高扩展性的应用。其适应性也很强,即可用于小型应用,也可用于大型复杂应用。事件驱动架构由高度解耦、单一用途的事件处理器组成,这些组件异步接收和处理事件。

事件驱动架构模式包含两种拓扑结构:调解器(mediator)拓扑和代理(broker)拓扑。当我们需要通过中央调解器在事件中编排多个步骤时,通常使用调解器拓扑,而当您希望在不使用中央调解器的情况下将事件链接在一起时,则使用代理拓扑。具体可参考其中博客:

事件驱动架构模式 | 众里千寻

下面是几种常用的事件驱动的架构模式:

1、消费与投影

针对那些使用非常广泛、已经成为瓶颈的服务

当有遗留服务存储着大型域对象的数据,这些数据使用又非常广泛,使得该遗留服务成为瓶颈时,此模式可以提供帮助。

在 Wix,MetaSite 服务就面临着这样的情况,它为 Wix 用户创建的每个站点保存了大量的元数据,比如站点版本、站点所有者以及站点上安装了哪些应用程序——已安装应用上下文(The Installed Apps Context.)。

这些信息对于 Wix 的许多其他微服务(团队)很有价值,比如 Wix Stores、Wix booking、Wix Restaurants 等等。这个服务被超过 100 万 RPM 的请求轰炸,它们需要获取站点元数据的不同部分。

从服务的各种 API 可以明显看出,它处理了客户端服务的太多不同的关注点。

MetaSite 服务处理大约 1M RPM 的各类请求

想要回答的问题是,如何以最终一致的方式将读请求从该服务转移出来?

使用 Kafka 创建“物化视图”

负责这项服务的团队决定另外创建一个服务,只处理 MetaSite 的一个关注点——来自客户端服务的“已安装应用上下文”请求。

首先,他们将所有数据库的站点元数据对象以流的方式传输到 Kafka 主题中,包括新站点创建和站点更新。一致性可以通过在 Kafka Consumer 中进行 DB 插入来实现,或者通过使用CDC产品(如Debezium)来实现。

其次,他们创建了一个有自己数据库的“只写”服务(反向查找写入器),该服务使用站点元数据对象,但只获取已安装应用上下文并写入数据库。即将站点元数据的某个“视图”(已安装的应用程序)投影到数据库中。

已安装应用上下文消费与投影

第三,他们创建了一个“只读”服务,只接受与已安装应用上下文相关的请求,通过查询存储着“已安装应用程序”视图的数据库来满足请求。

读写分离

效果

通过将数据以流的方式传输到 Kafka,MetaSite 服务完全同数据消费者解耦,这大大降低了服务和 DB 的负载。

通过消费来自 Kafka 的数据,并为特定的上下文创建一个“物化视图”,反向查找写入器服务能够创建一个最终一致的数据投影,大幅优化了客户端服务的查询需求。

将读服务与写服务分开,可以方便地扩展只读 DB 副本和服务实例的数量,这些实例可以处理来自全球多个数据中心的不断增长的查询负载。

2、端到端事件驱动

针对简单业务流程的状态更新

请求-应答模型在浏览器-服务器交互中特别常见。借助 Kafka 和WebSocket,就有了一个完整的事件流驱动,包括浏览器-服务器交互。

这使得交互过程容错性更好,因为消息在 Kafka 中被持久化,并且可以在服务重启时重新处理。该架构还具有更高的可伸缩性和解耦性,因为状态管理完全从服务中移除,并且不需要对查询进行数据聚合和维护。

考虑一下这种情况,将所有 Wix 用户的联系方式导入 Wix 平台。

这个过程涉及到两个服务:Contacts Jobs 服务处理导入请求并创建导入批处理作业,Contacts Importer 执行实际的格式化并存储联系人(有时借助第三方服务)。

传统的请求-应答方法需要浏览器不断轮询导入状态,前端服务需要将状态更新情况保存到数据库表中,并轮询下游服务以获得状态更新。

而使用 Kafka 和 WebSocket 管理者服务,可以实现一个完全分布式的事件驱动过程,其中每个服务都是完全独立工作的。

使用 Kafka 和 WebSocket 的 E2E 事件驱动

首先,浏览器会根据开始导入请求订阅 WebSocket 服务。

它需要提供一个 channel-Id,以便 WebSocket 服务能够将通知路由回正确的浏览器:

打开 WebSocket 通知“通道”

第二,浏览器需要向 Jobs 服务发送一个 HTTP 请求,联系人信息使用 CSV 格式,并附加 channel-Id,这样 Jobs 服务(和下游服务)就能够向 WebSocket 服务发送通知。注意,HTTP 响应将立即返回,没有任何内容。

第三,Jobs 服务在处理完请求后,会生成并向 Kafka 主题发送作业请求。

HTTP Import 请求和生成的 Import Job 消息

第四,Contacts Importer**服务消费来自 Kafka 的作业请求,并执行实际的导入任务。当它完成时,它可以通知 WebSocket 服务作业已经完成,而 WebSocket 服务又通知浏览器。

工作已消费、已处理和已完成状态通知

效果

使用这种设计,在导入过程的各个阶段通知浏览器变得很简单,而且不需要保持任何状态,也不需要任何轮询。

Kafka 的使用使得导入过程更具弹性和可扩展性,因为多个服务可以处理来自同一个原始导入 http 请求的作业。

使用 Kafka 复制,很容易将每个阶段放在最合适的数据中心和地理位置。也许导入器服务需要在谷歌 DC 上,以便可以更快地导入谷歌联系人。

WebSocket 服务的传入通知请求也可以生成到 Kafka,然后复制到 WebSocket 服务所在的数据中心。

3、内存 KV 存储

针对 0 延迟数据访问

有时,需要动态对应用程序进行持久化配置,但不想为它创建一个全面的关系数据库表。

一个选择是用HBase/Cassandra/DynamoDB为所有应用创建一个大的宽列存储表,其主键包含标识应用域的前缀(例如“store_taxes_”)。

这个解决方案效果很好,但是通过网络取值存在无法避免的延迟。它更适合于更大的数据集,而不仅仅是配置数据。

另一种方法是有一个位于内存但同样具有持久性的键/值缓存——Redis AOF提供了这种能力。

Kafka 以压缩主题的形式为键/值存储提供了类似的解决方案(保留模型确保键的最新值不会被删除)。

在 Wix,将这些压缩主题用作内存中的 kv-store,在应用程序启动时加载(消费)来自主题的数据。这有一个 Redis 没有提供的好处,这个主题还可以被其他想要获得更新的用户使用。

订阅和查询

考虑以下用例——两个微服务使用压缩主题来做数据维护:Wix Business Manager(帮助 Wix 网站所有者管理他们的业务)使用一个压缩主题存放支持的国家列表,Wix Bookings(允许安排预约和课程)维护了一个“(Time Zones)”压缩主题。从这些内存 KV 存储中检索值的延迟为 0。

各内存 KV 存储以及相应的 Kafka 压缩主题

Wix Bookings 监听“国家(Countries)”主题的更新:

Bookings 消费来自压缩主题 Countries 的更新

当 Wix Business Manager 将另一个国家添加到“国家”主题时,Wix Bookings 会消费此更新,并自动为“时区”主题添加一个新的时区。现在,内存 KV 存储中的“时区”也通过更新增加了新的时区:

South Sudan 的时区被加入压缩主题

Wix Events(供 Wix Users 管理事件传票和 RSVP)也可以使用 Bookings 的时区主题,并在一个国家因为夏令时更改时区时自动更新其内存 kv-store。

两个内存 KV 存储消费同一个压缩主题

4、调度并遗忘

当存在需要确保计划事件最终被处理的需求时

在许多情况下,需要 Wix 微服务根据某个计划执行作业。

Wix Payments Subscriptions 服务就是一个例子,它管理基于订阅的支付(例如瑜伽课程的订阅)。

对于每个月度或年度订阅用户,必须通过支付提供程序完成续订过程。

为此,Wix 自定义的 Job Scheduler 服务调用由 Payments Subscription 服务预先配置好的 REST 端点。

订阅续期过程在后台进行,不需要(人类)用户参与。这就是为什么最终可以成功续订很重要,即使临时有错误——例如第三支付提供程序不可用。

要确保这一过程是完全弹性的,一种方法是由作业调度器重复请求 Payment Subscriptions 服务(续订的当前状态保存在数据库中),对每个到期但尚未续期的订阅进行轮询。这将需要数据库上的悲观/乐观锁定,因为同一用户同一时间可能有多个订阅续期请求(来自两个单独的正在进行的请求)。

更好的方法是首先生成 Kafka 请求。为什么?因为请求的处理将由 Kafka 的消费者顺序完成(对于每个特定的用户),所以不需要并行工作的同步机制。

此外,一旦消息生成并发送到 Kafka,就可以通过引入消费者重试来确保它最终会被成功处理。由于有这些重试,请求调度的频率可能就会低很多。

在这种情况下,希望可以保持处理顺序,这样重试逻辑可以在两次尝试之间(以“指数退避”间隔进行)简单地休眠。

Wix 开发人员使用自定义的Greyhound消费者,因此,他们只需指定一个 BlockingPolicy,并根据需要指定适当的重试间隔。

在某些情况下,消费者和生产者之间可能会产生延迟,如长时间持续出错。在这些情况下,有一个特殊的仪表板用于解除阻塞,并跳过开发人员可以使用的消息。

如果消息处理顺序不是强制性的,那么 Greyhound 中还有一个使用“重试主题”的非阻塞重试策略。

当配置重试策略时,Greyhound 消费者将创建与用户定义的重试间隔一样多的重试主题。内置的重试生成器将在出错时生成一条下一个重试主题的消息,该消息带有一个自定义头,指定在下一次调用处理程序代码之前应该延迟多少时间。

还有一个死信队列,用于重试次数耗尽的情况。在这种情况下,消息被放在死信队列中,由开发人员手动审查。

这种重试机制是受 Uber这篇文章的启发。

Wix 最近开放了 Greyhound 的源代码,不久将提供给测试用户。要了解更多信息,可以阅读 GitHub 上的自述文件。

总结:Kafka 允许按顺序处理每个键的请求(例如使用 userId 进行续订),简化工作进程逻辑;

由于 Kafka 重试策略的实现大大提高了容错能力,续期请求的作业调度频率大大降低。

5、事务中的事件

当幂等性很难实现时

考虑下面这个典型的电子商务流程。

Payments 服务生成一个 Order Purchase Completed 事件到 Kafka。现在,Checkout 服务将消费此消息,并生成自己的 Order Checkout Completed 消息,其中包含购物车中的所有商品。

然后,所有下游服务(Delivery、Inventory 和 Invoices)将消费该消息并继续处理(分别准备发货、更新库存和创建发票)。

如果下游服务可以假设 Order Checkout Completed 事件只由 Checkout 服务生成一次,则此事件驱动流的实现会简单很多。

为什么?因为多次处理相同的 Checkout Completed 事件可能导致多次发货或库存错误。为了防止下游服务出现这种情况,它们将需要存储去重后的状态,例如,轮询一些存储以确保它们以前没有处理过这个 Order Id。

通常,这是通过常见的数据库一致性策略实现的,如悲观锁定和乐观锁定。

幸运的是,Kafka 为这种流水线事件流提供了一个解决方案,每个事件只处理一次,即使当一个服务有一个消费者-生产者对(例如 Checkout),它消费一条消息,并产生一条新消息。

简而言之,当 Checkout 服务处理传入的 Payment Completed 事件时,它需要将 Checkout Completed 事件的发送过程封装在一个生产者事务中,它还需要发送消息偏移量(使 Kafka 代理能够跟踪重复的消息)。

事务期间生成的任何消息将仅在事务完成后才对下游消费者(Inventory Service)可见。

此外,位于 Kafka 流开始位置的 Payment Service Producer 必须转变为幂等(Idempotent)生产者——这意味着代理将丢弃它生成的任何重复消息。

6、事件聚合

当想知道整个批次的事件已经被消费时

在上半部分,描述了在 Wix 将联系人导入到 Wix CRM 平台的业务流程。后端包括两个服务。一个是作业服务,提供一个 CSV 文件,它会生成作业事件到 Kafka。还有一个联系人导入服务,它会消费并执行导入作业。

假设 CSV 文件有时非常大,将工作负载分割成更小的作业,每个作业中需要导入的联系人就会更少,这个过程就会更高效。通过这种方式,这项工作可以在 Contacts Importer 服务的多个实例中并行。但是,当导入工作被拆分为许多较小的作业时,该如何知道何时通知最终用户所有的联系人都已导入?

显然,已完成作业的当前状态需要持久化,否则,内存中哪些作业已完成的记录可能会因为随机的 Kubernetes pod 重启而丢失。

一种在 Kafka 中进行持久化的方法是使用 Kafka 压缩主题。这类主题可以看成是一种流式 KV 存储。

在示例中,Contacts Importer 服务(在多个实例中)通过索引消费作业。每当它处理完一些作业,就需要用一个 Job Completed 事件更新 KV 存储。这些更新可以同时发生,因此,可能会出现竞态条件并导致作业完成计数器失效。

原子 KV 存储

为了避免竞态条件,Contacts Importer 服务将完成事件写到原子 KV 存储类型的 Jobs-Completed-Store 中。

原子存储确保所有作业完成事件将按顺序处理。它通过创建一个“Commands”主题和一个“Store”压缩主题来实现。

顺序处理

从下图可以看出,原子存储如何生成每一条新的 Import-job-completed“更新”消息,并以[Import Request Id]+[total job count]作为键。借助键,就可以总是依赖 Kafka 将特定 requestId 的“更新”放在特定的分区中。

接下来,作为原子存储的一部分,消费者-生产者对将首先侦听每个新的更新,然后执行 atomicStore 用户请求的“命令”——在本例中,将已完成作业数量的值加 1。

端到端更新流示例

回到 Contacts Importer 服务流。一旦这个服务实例完成了某些作业的处理,它将更新 Job-Completed KVAtomicStore(例如,请求 Id 为 YYY 的导入作业 3 已经完成):

Atomic Store 将生成一条新消息到 job-completed-commands 主题,键为 YYY-6,值为 Job 3 Completed。

接下来,Atomic Store 的消费者-生产者对将消费此消息,并增加 KV Store 主题中键 YYY-6 的已完成作业计数。

恰好一次处理

注意,“命令”请求处理必须只发生一次,否则完成计数器可能不正确(错误增量)。为消费者-生产者对创建一个 Kafka 事务(如上文的模式 4 所述)对于确保统计准确至关重要。

AtomicKVStore 值更新回调

最后,一旦 KV 最新生成的已完成作业计数的值与总数匹配(例如 YYY 导入请求有 6 个已完成作业),就可以通知用户(通过 WebSocket,参见本系列文章第一部分的模式 3)导入完成。通知可以作为 KV-store 主题生成动作的副作用,即调用用户提供给 KV 原子存储的回调。

注意事项:

完成通知逻辑不一定要在 Contacts Importer 服务中,它可以在任何微服务中,因为这个逻辑完全独立于这个过程的其他部分,只依赖于 Kafka 主题。

不需要进行定期轮询。整个过程都是事件驱动的,即以管道方式处理事件。

通过使用基于键的排序和恰好一次的 Kafka 事务,避免作业完成通知或重复更新之间的竞态条件。

Kafka Streams API 非常适合这样的聚合需求,其特性包括 groupBy(按 Import Request Id 分组), reduce 或 count(已完成作业计数)和 filter (count 等于总作业数),然后是副作用 Webhook 通知。对于 Wix 来说,使用现有的生产者/消费者基础设施更有意义,这对微服务拓扑影响更小。

总结

这里的一些模式比其他的模式更为常见,但它们都有相同的原则。通过使用事件驱动的模式,可以减少样板代码(以及轮询和锁定原语),增加弹性(减少级联失败,处理更多的错误和边缘情况)。此外,微服务之间的耦合要小得多(生产者不需要知道谁消费了它的数据),扩展也更容易,向主题添加更多分区(和更多服务实例)即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/795089.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】考研真题攻克与重点知识点剖析 - 第 3 篇:栈、队列和数组

前言 本文基础知识部分来自于b站:分享笔记的好人儿的思维导图与王道考研课程,感谢大佬的开源精神,习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术…

LogicFlow 在HTML中的引入与使用

LogicFlow 在HTML中的引入与使用 LogicFlow的引入与使用,相较于BPMNJS相对容易一些,更加灵活一些,但是扩展代码可能写得更多一些。 示例展示 使用方式 这个的使用方式就简单很多了,利用cdn把js下载下来,引入到HTML文…

c语言之向main函数传递参数

在c语言中&#xff0c;main函数也是可以传递传递参数的&#xff0c;业内向main函数传递参数的格式是 main(int argc,char *argv[]) 向main函数传递参数不是通过代码传递的&#xff0c;一般是通过dos命令传递 举个例子 #include<stdio.h> void main(int argc,char *ar…

PyTorch之计算模型推理时间

一、参考资料 如何测试模型的推理速度 Pytorch 测试模型的推理速度 二、计算PyTorch模型推理时间 1. 计算CPU推理时间 import torch import torchvision import time import tqdm from torchsummary import summarydef calcCPUTime():model torchvision.models.resnet18()…

使用pip install替代conda install将packet下载到anaconda虚拟环境

问题描述 使用conda install 下载 stable_baseline3出现问题 一番搜索下是Anaconda.org缺少源 解决方法 首先使用管理员权限打开 anaconda prompt 然后激活目标环境&#xff1a;conda activate env_name 接着使用&#xff1a;conda env list查看目标env的位置 如D:\anacon…

2023.4.7 机器学习周报

目录 引言 Abstract 文献阅读 1、题目 2、引言 3、过去方案和Motivation 4、Segment Anything模型 5、创新点 6、实验过程 7、实验结果 1、评价绩效 2、检测评价 3、跟踪评价 8、 结论 总结 引言 本周阅读了一篇关于高效的任意分割模型的文献&#xff0c;用于自…

JVM基础:类的生命周期详解

JDK版本&#xff1a;jdk8 IDEA版本&#xff1a;IntelliJ IDEA 2022.1.3 文章目录 一. 生命周期概述二. 加载阶段(Loading)2.1 加载步骤2.2 查看内存中的对象 三. 连接阶段(Linking)3.1 连接之验证3.2 连接之准备3.3 连接阶段之解析 四. 初始化阶段(Initialization)4.1 单个类的…

Arcgis Pro地理配准

目录 一、目的 二、配准 1、找到配准工具 2、添加控制点 3、选择控制点 4、添加更多控制点 5、配准完成、保存 三、附录 1、查看控制点或删除控制点 2、效果不好怎么办 一、目的 下面我们将两张地图进行配准&#xff0c;其中一张有地理位置&#xff0c;而另外一张没…

前端面试高频: 理解 React/Vue 中 Key 的作用

一: 引言 在 React 或 Vue 项目中&#xff0c;我们经常在列表组件中使用key属性。key是给每一个vnode的唯一id&#xff0c;它在列表渲染和虚拟 DOM 操作中扮演着重要的角色。 当我们在渲染一个包含多个相同子组件的列表时&#xff0c;如果没有使用 key&#xff0c;React 或 Vue…

666666666666666666

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Linux (Ubuntu)- mysql8 部署

目录 1.基本部署 2.修改密码 3.开启root可远程连接配置 1.基本部署 01》》先查看OS类型&#xff0c;如果是Ubuntu在往下边看 rootspray:/etc/mysql/mysql.conf.d# lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: …

备战蓝桥杯---线段树应用2

来几个不那么模板的题&#xff1a; 对于删除&#xff0c;我们只要给那个元素附上不可能的值即可&#xff0c;关键问题是怎么处理序号变化的问题。 事实上&#xff0c;当我们知道每一个区间有几个元素&#xff0c;我们就可以确定出它的位置&#xff0c;因此我们可以再维护一下前…

RK3568测试

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

非关系型数据库------------Redis的安装和部署

目录 一、关系型数据库与非关系型数据库 1.1关系型数据库 1.2非关系型数据库 1.2.1非关系型数据库产生背景 1.3关系型非关系型区别 1.4客户访问时&#xff0c;关系型数据库与redis的工作过程 二、Redis 2.1redis简介 2.2Redis命中机制和淘汰机制 2.3Redis 具有以下优…

树莓派安装Windows搭建网盘和下载机

0 需求分析 在同一个局域网内&#xff0c;同时有多种设备&#xff08;Windows&#xff0c;Linux&#xff0c;Android&#xff09;需要进行大量的数据共享。另外&#xff0c;还时常需要从百度网盘/夸克网盘等网盘下载文件。不难看出&#xff0c;我的需求很简单&#xff0c;就是…

提醒:2024年思维100春季线上比赛今天报名截止,来看官方样题

【温馨提醒】今天是2024年4月6日&#xff0c;是2024年春季思维100活动第一阶段线上比赛报名的截止日期。如果要参赛&#xff0c;请设置好闹钟别错过了报名。更多安排和需要提前了解的关键点可以见我前面写的文章&#xff0c;或者直接联系我获取相关资料。 官方发布的2024年春季…

汽车网络安全管理

汽车网络安全管理 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c…

javaweb学习(day11-监听器Listener过滤器Filter)

一、监听器Listener 1 Listener介绍 Listener 监听器它是 JavaWeb 的三大组件之一。JavaWeb 的三大组件分别是&#xff1a;Servlet 程 序、Listener 监听器、Filter 过滤器 Listener 是 JavaEE 的规范&#xff0c;就是接口 监听器的作用是&#xff0c;监听某种变化(一般就是对…

Java零基础入门-递归

一、概述 上一期&#xff0c;我们是具体学习了File类的一些概念基础知识点&#xff0c;以及对于该类的常用方法进行了一个全量举例演示&#xff0c;这也是考虑到有的小伙伴在阅读的同时&#xff0c;没有时间去实际测试&#xff0c;所以我也就顺带的给大家去做了实例演示&#x…

数据生成 | Matlab实现基于K-means和SVM的GMM高斯混合分布的数据生成

数据生成 | Matlab实现基于K-means和SVM的GMM高斯混合分布的数据生成 目录 数据生成 | Matlab实现基于K-means和SVM的GMM高斯混合分布的数据生成生成效果基本描述模型描述程序设计参考资料 生成效果 基本描述 1.Matlab实现基于K-means和SVM的GMM高斯混合分布的数据生成&#xf…