工赋开发者社区 | 事件驱动架构要避开的 5 个陷阱

news2024/11/12 11:46:45

事件驱动架构非常强大,非常适合用在分布式微服务环境中。事件驱动架构提供了解耦的架构、更容易实现的可伸缩性和更高程度的弹性。

请求应答(客户端和服务器)与事件流(发布和订阅)

但是,与请求和应答类型的架构相比,正确使用事件驱动架构要困难得多。

在过去的几年里,我们一直在逐步将我们不断增长的微服务(目前有 2300 个)从请求和应答模式迁移到事件驱动架构。下面是 Wix 工程师在实验事件驱动架构时遇到的 5 个陷阱。

这些陷阱让我们遭遇了生产事故,给我们带来了巨大痛苦,我们不得不进行重写,还得面对陡峭的学习曲线。对于每一个陷阱,我都提供了已经在 Wix 使用的经过实战验证的解决方案。

写入数据库再触发事件

(非原子操作)

我们以一个简单的电子商务流程为例(我们将在本文中使用这个示例)。

在支付处理完成后,应该更新商品库存,表示为客户保留商品。

写入数据库和产生事件是非原子操作

问题在于,将支付完成状态写入数据库,然后向 Kafka(或其他消息代理)生成“支付完成”事件并不是一个原子操作。在某些情况下,可能只有其中一个动作执行成功。

例如,数据库不可用或 Kafka 不可用可能会导致分布式系统不同部分之间的数据不一致。在这种情况下,库存可能与实际订单不一致。

原子性补救 1——Greyhound 弹性生产者

有几种方法可以缓解这个问题。在 Wix,我们使用了两种方式。第一种是使用我们自己的消息平台 Greyhoundhttps://github.com/wix/greyhound#greyhound,我们通过弹性生产者确保事件最终被写入 Kafka。这种缓解措施的缺点是最终可能会导致对下游事件的无序处理。Greyhound

Greyhound 生产者回退到 S3,一个将消息恢复到 Kafka 的专用服务

原子性补救 2——Debezium Kafka 源连接器

第二种确保数据库更新动作和 Kafka 生成动作都发生并且数据保持一致的方法是使用 Debezium Kafka 连接器。Debezium 连接器可以自动捕获数据库中发生的变更事件(CDC),并将它们生成到 Kafka 主题中。

使用 Debezium 数据库连接器和 Kafka Connect 结合使用可以保证事件最终被生成到 Kafka。此外,还可以保持事件的顺序。

Debezium 连接器确保变更事件最终与数据库保持一致

需要注意的是,Debezium 也支持其他事件流平台,如 Apache Pulsar。

事件溯源无处不在

在事件溯源模式中,服务不是在业务操作时更新实体的状态,而是将事件保存到数据库中。服务通过重放事件来重建实体的状态。

这些事件也被发布到事件总线上,其他服务也可以在其他数据库上创建物化视图,这些数据库通过重放事件优化查询。

事件溯源——将变更事件持久化到事件存储中,通过重放事件重建状态

虽然这种模式有一定的优点(可靠的审计日志、实现“时间旅行”——能够在任何时间点获取实体的状态,并在相同的数据上构建多个视图),但到目前为止,它比更新保持在数据库中的实体状态的 CRUD 服务要复杂得多。

事件溯源的缺点

  • 复杂性——为了确保读取性能不受重放事件的影响,必须不时地获取实体状态快照,以减少性能损失。这增加了系统的复杂性,因为后台进程可能会出问题。当后台进程出问题时,数据可能是过时的。最重要的是,拥有两个数据副本意味着它们可能会不同步。

  • 雪花属性——与 CRUD ORM 解决方案不同,事件溯源很难创建通用库和框架来简化开发并全局解决适合每一个应用场景的快照和读取优化。

  • 只支持最终一致性(不适合写后读的场景)。

事件溯源替代方案——CRUD + CDC

利用简单的 CRUD 和向下游发布数据库变更事件(例如创建查询优化的物化视图)可以降低复杂性,增加灵活性,并仍然可以在特定用例中实现命令查询责任隔离(CQRS)。

对于大多数场景,服务可以公开一个简单的读取端点,这个端点从数据库获取实体的当前状态。随着规模的扩大,需要更复杂的查询,这个时候可以使用额外发布的变更事件来创建专门为复杂查询定制的物化视图。

CRUD——简单地读取数据库 + 用于外部物化视图的 CDC

为了避免将数据库变更作为契约暴露给其他服务,并在它们之间创建耦合,服务可以读取 CDC 主题并生成变更事件的“官方”API,类似于在事件溯源模式中创建的事件流。

无上下文传播

切换到事件驱动架构意味着开发人员、DevOps 和 SRE 在调试产品问题和跟踪用户请求方面可能存在更大的困难。

与请求和应答模型不同,事件驱动架构没有可跟踪的 HTTP/RPC 请求链。调试代码变得更加困难,因为事件处理代码分散在服务代码中,无法通过简单地单击对象或模块的函数定义进行跟踪。

我们仍然以本文中使用的电子商务流程为例。订单服务必须使用多个来自 3 个不同主题的事件,这些事件都与同一个用户操作(在网商购买商品)相关。

完全事件驱动的微服务很难跟踪请求流

其他服务也使用来自一个或多个主题的多个事件。我们假设某些商品的库存水平是不正确的,这个时候,调查所有相关订单事件的处理就变得至关重要。否则,我们需要花很长时间查看各个服务的日志,并尝试手动将不同的证据片段连接在一起。

自动上下文传播

自动为所有事件添加请求上下文使得过滤与用户请求相关的事件变得非常简单。在我们的示例中,添加了 2 个事件标头——requesttid 和 userId。这两个标识都能极大地帮助我们进行问题诊断。

为每个事件自动附加用户请求上下文,便于跟踪和调试

在 Wix,当事件被生成和消费时,Greyhound 会自动传播用户请求上下文。此外,我们还可以在日志中找到请求上下文,这样就可以针对特定的用户请求过滤日志。

发布包含大消息体的事件

在处理包含大消息体的事件(大于 5MB,例如图像识别、视频分析等)时,人们可能会倾向于将它们发布到 Kafka(或 Pulsar),但这可能会大大增加延迟、降低吞吐量并增加内存压力(特别是当不使用分层存储时 0。

幸运的是,有几种方法可以克服这个问题,包括压缩、将消息体拆分为块、将消息体放入对象存储并只在流式平台中传递引用。

大消息体补救措施 1——压缩

Kafka 和 Pulsar 都支持压缩消息体。你可以尝试几种压缩算法(lz4、snappy 等),找到最适合你的压缩算法。如果消息体比较大(最多 5MB), 50% 的压缩率可以帮你保持消息代理集群良好的性能。

Kafka 级别的压缩通常比应用程序级别的更好,因为消息体可以批量压缩,从而提高压缩比。

大消息体补救措施 2——分块

减少代理压力和覆盖消息大小限制的另一种方法是将消息分割为块。

分块是 Pulsar 的内置功能(有一些限制),但对于 Kafka 来说,分块必须发生在应用程序级别。

如何在应用程序级实现分块的示例可以在这里https://medium.com/wix-engineering/chunks-producer-consumer-f97a834df00d(https://medium.com/wix-engineering/chunks-producer-consumer-f97a834df00d)和这里https://medium.com/workday-engineering/large-message-handling-with-kafka-chunking-vs-external-store-33b0fc4ccf14找到。基本前提是生产者发送带有额外元数据的数据块,帮助消费者重新组装它们。

生产者将数据分成块,消费者将其组装复原

这两种示例方法的不同之处在于它们如何组装数据块。第一个示例将数据块保存在某个持久存储中,当所有数据块都生成后,消费者一次性获取所有数据块。第二个示例让消费者在所有数据块到达后在主题分区中向后查找第一个数据块。

大消息体补救措施 3——使用对象存储的引用

最后一种方法是简单地将消息体内容存储在对象存储中(如 S3),并将对象的引用(通常是 URL)作为事件的消息体。这些对象存储允许在不影响第一个字节延迟的情况下持久化任何所需的大小。

在生成链接之前,需要确保消息体内容已经完全上传到对象存储中,否则消费者需要不断重试,直到可以开始下载它。

不处理重复事件

大多数消息代理和事件流平台默认保证至少一次传递,这意味着一些事件可能出现重复,或者可能会被处理两次(或多次)。

确保重复事件的副作用只发生一次叫作幂等性。

我们继续以本文中一直使用的电子商务流程为例。由于一些处理错误导致需要进行重复处理,记录到库存数据库中的已购买商品的库存量下降得可能比实际的要多一些。

消费者多次处理导致库存变得不正确

其他副作用包括多次调用第三方 API(在我们的示例中,这可能意味着对相同的事件和商品两次调用降低库存数量的服务)。

等幂补救——revisionId(版本控制)

在需要等幂处理的情况下,可以考虑使用乐观锁技术。在发生更新之前需要先读取存储实体的当前 revisionId(或版本),如果有多方尝试同时更新实体(同时增加版本),那么第二个尝试更新的一方将失败,因为版本与之前读取的不匹配。

在对重复事件进行幂等处理时,revisionId 必须是唯一的,并且是事件本身的一部分,这样可以确保两个事件不共享相同的 id,并且针对同一 revisionId 的第二次更新将(静默地)失败。

为每个事件附加 transactionId,避免重复处理

特别是在使用 Kafka 时,有可能配置精确一次语义,但由于某些故障,数据库更新仍然可能出现重复。在这种情况下,txnId 可以是主题 - 分区 - 偏移量三元组,这样可以保证 id 是唯一的。

总 结

迁移到事件驱动架构可以是一个循序渐进的过程,这样可以减少与之相关的风险,比如调试困难和心里层面的复杂性。微服务架构允许为每个不同的服务灵活地选择模式。HTTP/RPC 端点可以作为事件处理的一部分进行调用,反之亦然。

作为这种渐进迁移的结果,我强烈建议采用 CDC 模式,将其作为一种既能确保数据一致性(陷阱 1)又能避免与完全成熟的事件溯源相关的复杂性和风险(陷阱 2)的方法。CDC 模式仍然允许将请求和应答模式与事件处理模式结合在一起。

解决陷阱 3(在事件流中传播用户请求上下文)将大大提高快速查找生产事故根源的能力。

陷阱 4 和陷阱 5 的补救措施是针对具体场景的——陷阱 4 的消息体非常大,而陷阱 5 的副作用不是幂等的。如果没有必要,就不需要做出这些变更。尽管作为最佳实践,可以使用压缩(陷阱 4)和事务 ID(陷阱 5)。

原文链接:

https://natansil.medium.com/event-driven-architecture-5-pitfalls-to-avoid-b3ebf885bdb1

扫描上方二维码

即可进社区交流群

· E小萌 ·

添加小助手微信

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

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

相关文章

防范DDOS攻击的最佳方法

Botnets是由多个感染的计算机(称为"bots"或"zombies")组成的网络。这些感染的计算机受到攻击者的控制,可以同时发送大量的数据流量到目标网站,导致DDoS(分布式拒绝服务)攻击。Botnets也可以用于从感染的计算机中获取私人信息,传播病毒和蠕虫,或者执…

Git使用个人访问令牌提交代码到仓库

文章目录前言解决方法1、创建自己的token2.1、使用token(classic)2.2、使用token(方法二)参考链接前言 2021年8月13日开始,GitHub在使用Git操作远程仓库时不再支持使用用户名密码形式进行身份验证,需使用个人访问令牌。 使用Git操作GitHub仓库时使用用户…

整合spring cloud云服务架构 - 企业分布式微服务云架构构建

1. 介绍 Commonservice-system是一个大型分布式、微服务、面向企业的JavaEE体系快速研发平台,基于模块化、服务化、原子化、热插拔的设计思想,使用成熟领先的无商业限制的主流开源技术构建。采用服务化的组件开发模式,可实现复杂的业务功能。…

电脑开不了机怎么办?排查这3种情况

电脑开不了机不一定是电脑故障损坏的问题,也可能是硬件或者是线路松动导致的。电脑开不了机的原因有很多,电脑开不了机怎么办?我们先来了解下主要是哪些原因,排查出原因才好对症下药。 操作环境: 演示机型:…

Leetcode刷题注意点

时间复杂度:一个句子被执行多少次。 空间复杂度:一个算法在运行过程中临时占用存储空间大小的量度 列表可以用.append() 添加数据 字符串 用 s q 这个写法。 03 此题关键是哈希表的运用。 dic set() 表示取出nums中的key 并且set里面元素不重复 53…

python多光谱遥感数据处理、图像分类、定量评估及机器学习方法应用

普通数码相机记录了红、绿、蓝三种波长的光,多光谱成像技术除了记录这三种波长光之外,还可以记录其他波长(例如:近红外、热红外等)光的信息。与昂贵、不易获取的高光谱、高空间分辨率卫星数据相比,中等分辨…

2023年广州Java培训机构排行榜来啦,快来围观!

到底哪个Java培训机构靠谱?Java培训怎么选?如何在千万培训机构中一眼寻觅到自己的唯一所爱?别疑惑,别迷茫,小编带着2023最新数据来了,保证你看完有“山重水复疑无路,柳暗花明又一村”的豁然开朗感&#xf…

在线 OJ 项目(四) · 前端设计与项目总结

一、页面设计题目列表页题目详情页二、获取到后台数据实现思路遇到换行问题小结引入 ace.js三、项目总结接下来将实现 online-oj 项目的前端界面。 先随便从各大网站上下载网页模板~ 因为好看的前端网页设计需要美工的,我们只是程序员… 一、页面设计 但是我们可…

【可解释性机器学习】详解Python的可解释机器学习库:SHAP

详解Python的可解释机器学习库:SHAPSHAP介绍SHAP的用途SHAP的工作原理解释器Explainer局部可解释性Local Interper单个prediction的解释多个预测的解释获取单个样本的Top N个特征值及其对应的SHAP值全局可解释性Global Interpersummary_plotFeature ImportanceInter…

SkyWalking 极简入门

SkyWalking 极简入门1.概述1.1 概念1.2 功能列表1.3 整体架构1.4 官方文档2. 搭建 SkyWalking 单机环境2.1 Elasticsearch 搭建2.2 下载 SkyWalking 软件包2.3 SkyWalking OAP 搭建2.4 SkyWalking UI 搭建2.5 SkyWalking Agent2.5.1 Shell2.5.2 IDEA3. 搭建 SkyWalking 集群环境…

【4】Linux实用操作

学习笔记目录 初识Linux--入门Linux基础命令--会用Linux权限管控--懂权限Linux实用操作--熟练实战软件部署--深入掌握脚本&自动化--用的更强项目实战--学到经验云平台技术--紧跟潮流 各类小技巧(快捷键) ctrl c强制停止 Linux某些程序的运行&am…

AI作画:文心一格赋能艺术与设计创作

针对视觉内容创作门槛高、耗时长等行业痛点问题,百度推出了基于文心大模型的AI艺术创作产品文心一格。通过文心一格核心系统的技术创新,让AI作画普惠大众,提升创作效率。目前,文心一格产品已经对外发布使用,大众用户均…

Docker容器基本操作

docker中的容器就是一个轻量级的虚拟机,是镜像运行起来的一个状态,本文就先来看看容器的基本操作。 查看容器 查看容器 启动docker后,使用docker ps命令可以查看当前正在运行的容器: 查看所有容器 上面这条命令是查看当前正在…

[强网杯 2019]高明的黑客

目录 信息收集 正则测试 python脚本 getshell 信息收集 $_GET[ganVMUq3d] ; eval($_GET[ganVMUq3d] ?? ); $_GET[jVMcNhK_F] ; system($_GET[jVMcNhK_F] ?? ); $_GET[cXjHClMPs] ; echo {$_GET[cXjHClMPs]}; 下载gz解压后得到几千个php文件,简单看…

【项目实战】count(1) 、count(col)、count(*) 如何选择?

一、背景 有时候会看业务执行的情况,如查看多少用户已经领取了礼品等,需要看数据库的计数或统计用户使用情况时,往往会使用聚合函数COUNT(),聚合函数有很多种,列出如官网的截图 而其中常用的聚合函数主要是包括以下&…

Linux常用命令——readelf命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) readelf 用于显示elf格式文件的信息 补充说明 readelf命令用来显示一个或者多个elf格式的目标文件的信息,可以通过它的选项来控制显示哪些信息。这里的elf-file(s)就表示那些被检查的文件。可以支持…

java基于ssm滑雪场门票在线售票系统的设计与实现

基于jsp技术设计并实现了滑雪售票系统。该系统基于B/S即所谓浏览器/服务器模式,应用SSM框架,选择MySQL作为后台数据库。系统主要包括个人中心、用户管理、票务信息管理、购票信息管理、技巧交流、系统管理等功能模块。 性能测试主要通过模拟系统运行环境…

蓝桥杯刷题017——轨道炮(贪心)

2019国赛轨道炮 题目描述 小明在玩一款战争游戏。地图上一共有 N 个敌方单位,可以看作 2D 平面上的点。其中第 i 个单位在 0 时刻的位置是 (Xi​,Yi​),方向是 Di​ (上下左右之一, 用U/D/L/R 表示),速度是 Vi​。 小明的武器是轨道炮&#x…

我来告诉你,ChatGPT 该怎么对接到自己的项目中!

作者:小傅哥 博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获!😄 1. 项目介绍 《ChatGPT AI 问答助手》 开源免费项目,涵盖爬虫接口、ChatGPT API对接、DDD架构设计、镜像打包、Dock…

六十年间中国经济总量增长245倍

中国GDP(现价美元)走势图回顾2022年,中国经济顶住了来自防控疫情及国际变化的巨大压力,全年GDP实现3%的同比增长,其中年末冬季GDP同比增长2.9%。从主要指标显示,12月第三产业、投资、地产都有不同程度的改善…