重视和解决 ABP 分布式事件乱序问题

ABP Framework 5.0 实现了单体应用场景下,收件箱和发件箱的事件严格顺序性。但在微服务或多数据库场景下,由于网络时延和设施效率的限制, 分布式事件将不再是 Linearizability [1] 的,因此必然会存在物理时间上的收件乱序。

借用 Daniel Wu 的文章《消息可靠性和顺序(中文)》[2] 中的插图为您展示问题:

0a44612e14d1521097aab983e2bc310c.png

如果一个处理中的事件,与任何其他的事件之间有因果关系,则有可能因两者的乱序而产生问题。

本文在这个事实下,讨论我们在订阅方可能遇到的情况和解决方案。

案例

我们做以下假设。

  1. 我们关注的是一个用户积分服务,它是一些分布式事件的订阅方。

  2. m1 和 m2 是 先后发生 的两个事件。

  3. t1 和 t2 分别为订阅方服务收到并处理事件 m1 和 m2 的时间。

  4. t1 < t2 代表 t1 早于 t2,称为正序;t1 > t2 代表 t1 晚于 t2,称为乱序。

  5. C 代表订阅方服务的状态(Configuration)。C0 为初始状态,CF 为预期的最终状态,CW 为错误的最终状态。

案例 1

  • 事件 m1:用户 A 创建事件

  • 事件 m2:用户 B 创建事件

  • Handler 的工作:根据 m1 和 m2,分别在本地创建 LocalUser 实体

  • 分析:m1 和 m2 没有因果关系,顺序不敏感

    • t1 < t2 (正序):

      89d821e6c971946bea09ffae22a30cb1.png

    • t1 > t2 (乱序):

      4b2b7cd11719f77050b53b427ce4eac9.png

无需处理。

案例 2

  • 事件 m1:用户 A 创建事件

  • 事件 m2:订单 1 支付事件

  • Handler 的工作:根据 m1,在本地创建LocalUser实体;根据 m2,给LocalUser.Score增加积分

  • 分析:m1 和 m2 有因果关系。m1 和 m2 顺序敏感,但“实体不存在”的异常拦截了乱序,handler 是幂等的,不存在一致性问题

    • t1 < t2 (正序):

      524ccb5fef7d4d0922ad14dfeeed7b5d.png

    • t1 > t2 (乱序):

      e8e6630af81b7beacef4158c1423c69e.png

无需处理。待 m1 被处理后,m2 延迟重试处理,实质上达到正序。

案例 3

  • 事件 m1:订单 1 支付事件

  • 事件 m2:订单 1 取消事件

  • Handler 的工作:根据 m1,给LocalUser.Score增加积分;根据 m2,给LocalUser.Score扣减积分;积分最低扣到 0,不会为负数

  • 分析:m1 和 m2 有因果关系。m1 和 m2 顺序敏感,handler 不是幂等的,存在一致性问题

    • t1 < t2 (正序):

      34febdd19007539992753741f8b1adae.png

    • t1 > t2 (乱序):

      a92ec87f4ba0a16928534938de2b47b2.png

积分服务在本地创建LocalOrder实体记录订单处理状态。

public class LocalOrder : AggregateRoot<Guid>
{public bool HasPaidEventHandled { get; set; } // set to true after handling m1
}

当 m2 handler 发现OrderCanceledEto.OrderPaidTime != nullLocalOrder.HasPaidEventHandled == false,则抛出错误。待 m1 被处理后,m2 延迟重试处理,实质上达到正序。

我们实质上把本案例 3 转化成了案例 2 的情况,从而实现了幂等。

处理后

  • t1 < t2 (正序):

    692652458c800b4be237200a5a5aed97.png

  • t1 > t2 (乱序):

    000d0babe6e07fd8b87893c8ca13eeb5.png

案例 4

  • 事件 m1:用户 A 变更事件 (变更了可用区 Region)

  • 事件 m2:订单 1 支付事件

  • Handler 的工作:根据 m1,由于UserEto.Region != LocalUser.Region,清零LocalUser.Score。根据 m2,给LocalUser.Score增加积分

  • 分析:m1 和 m2 有因果关系。m1 和 m2 顺序敏感,handler 不是幂等的,存在一致性问题

    • t1 < t2 (正序):

      6789eed2c0d59604bc5ebe87900d126f.png

    • t1 > t2 (乱序):

      68fcfa8b93be15748cc6bfce752ab428.png

我们可以通过这些改动解决问题:

  1. User实体扩展 int 类型属性RegionVersion,默认值为 0,每次 Region 变更时,RegionVersion递增 1。

  2. 积分服务使用LocalUserRegion.Score记录用户的积分,而非使用LocalUser.Score

    public class LocalUserRegion : AggregateRoot<Guid>
    {public Guid UserId { get; set; }public string Region { get; set; }public int RegionVersion { get; set; }public int Score { get; set; }
    }
  3. 处理 m1 时,若UserEto.RegionVersion更新,则创建新的LocalUserRegion实体,初始的积分为 0,相当于变更 Region 即清零积分。

  4. 在用户支付时,本地服务调用 Identity 远程服务,将查得的UserDto.RegionVersion写入事件 m2 的OrderPaidEto.UserRegionVersion

  5. 处理 m2 时,根据OrderPaidEto.UserRegionVersion,增加对应的LocalUserRegion.Score

我们解除了 m1 和 m2 的因果关系,从而实现了幂等。

处理后

  • t1 < t2 (正序):

    216305b084dd214768e0e64e53ab2199.png

  • t1 > t2 (乱序) :

    0bcef0440d769580ae53b50c5742f225.png

案例 5:ABP 实体同步器

在 ABP 的 DDD 实践中,不同模块之间会通过实体同步器冗余实体数据。一个典型的案例是 Blogging 模块的 BlogUserSynchronizer [3]。本案例的特别之处在于,如果不是有极严的要求,过期的事件可以被跳过处理。

  • 事件 m1:用户 A 变更事件

  • 事件 m2:用户 A 变更事件

  • Handler 的工作:根据 m1/m2,更新LocalUser实体中的用户资料

  • 分析:一旦 m2 早于 m1 被处理,则旧资料会覆盖新资料,存在一致性问题

    • t1 < t2 (正序):

      e355ade7bff988846b213ca47f1e2b6d.png

    • t1 > t2 (乱序):

    • b37be5acb993a5e46736363dd9926d65.png

我们给实体增加 int 类型的 EntityVersion 属性,此属性的值从 0 开始,并在每次更新实体时,自动递增 1。在实体同步器处理 EntityUpdatedEto<UserEto> 事件时,若 UserEto.EntityVersion <= LocalUser.EntityVersion,则跳过处理。就这样,我们解决了问题。我尝试了在 ABP 框架实现以上能力,见 PR #14197 [4]。

处理后

  • t1 < t2 (正序):

    93aab3c0337df61aea186b075e5c471a.png

  • t1 > t2 (乱序):

    74eb496ebb2a36a8984d79af02016026.png

思路整理

笔者认为,解决事件乱序问题有以下思路。

  1. 尽可能保持 DistributedEventHandler 的业务逻辑简单,以便发现潜在的乱序问题。

  2. 某些情况下,我们可以通过在本地记录实体的状态,将 handler 转化为幂等,就如上面案例 3 演示的那样。

  3. 某些情况下,我们可以通过调整业务设计,解除因果关系,就如上面案例 4 演示的那样。

  4. 实体同步器应采用 EntityVersion 的设计,以避免同步到过期的数据。

结论

即使你的应用当前只是单体,也应关心收件乱序问题,为今后可能到来的架构变化做储备。另外,请放弃实现 Linearizability,因为在微服务或多数据库场景下这是不可能的。

本文提到的几个案例,开发者似乎不难找出一致性问题的隐患。但在实际生产中,业务往往更复杂,事件数量也会更多,我们很难顾及周全。即便我们在开发时把所有可能的因果关系都找了出来,并且处理了它们,将来业务变更时,我们还能确保万无一失吗?答案恐怕是否定的。

分布式一致性问题是没有银弹的,它永远都在那里,开发者能做的是降低复杂度,通过设计解除因果关系,或手动实现幂等。

参考

  1. Herlihy, Maurice P.; Wing, Jeannette M. (1987). "Axioms for Concurrent Objects". Proceedings of the 14th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages, POPL '87. p. 13

  2. Daniel Wu. (2021). 《消息可靠性和顺序(中文)》. https://danielw.cn/messaging-reliability-and-order-cn

  3. GitHub abpframework/abp repository. BlogUserSynchronizer.cs. https://github.com/abpframework/abp/blob/1275f2207fc39d850d23472294e456c8504f20d2/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs

  4. GitHub abpframework/abp repository. PR #14197. abpframework/abp#14197

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

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

相关文章

个人博客建站方案推荐

1.服务器选择 正值双十一来临之际各大服务器提供商又大量的优惠活动&#xff0c;各位要步入个人站长行列的小哥们时机要把握好了&#xff0c;我个人使用过阿里云的服务器&#xff0c;腾讯云的服务器&#xff0c;华为云的服务器。其实&#xff0c;个人感觉就放个博客&#xff0c…

android启用hdcp_如何在Android上启用优先收件箱(和设置仅重要通知)

android启用hdcpYesterday Google released an updated Gmail application for Android 2.2 phones that supports the Priority Inbox feature—and more importantly, allows you to change your notifications to only alert you for important email. Let’s take a look. …

.Net CLR GC plan_phase二叉树和Brick_table

楔子Plan_Phase(GC的计划阶段)很早就接触了&#xff0c;但是后面一直没用到&#xff0c;忘记了&#xff0c;此次又用到了&#xff0c;几乎忘光了&#xff0c;费了很大力气理解它&#xff0c;记录下&#xff0c;以免又忘记了。主题计划阶段(plan_phase)主要就两个部分&#xff0…

scrapy爬虫启示录-小伙子老夫看你血气方刚这本《爬虫秘录》就传给你了

文章来源&#xff1a; IT源点 第一章 误入歧途 每个学习爬虫的人都有一颗爱美的心&#xff0c;俺也是一样的。那么多的美眉图片&#xff0c;不薅下来&#xff0c;没了谁负责。于是夜里孤枕难眠的老男孩开始了他的撸码之旅。从此在学习爬虫&#xff0c;学习Python的道路上越走…

自己设置假期的日历控件_在假期旅行时使用PC娱乐自己

自己设置假期的日历控件Staying connected may be hard no matter what network you are on, and in flight Wi-Fi isn’t pervasive enough to count on. Here are tips and tricks to keep yourself entertained when unplugged and traveling. 无论您使用什么网络&#xff0…

.Net CLR异常和windows C++ 异常调用栈简析

楔子前面一篇研究了下C异常的&#xff0c;这篇来看下&#xff0c;CLR的异常内存模型&#xff0c;实际上都是一个模型&#xff0c;承继自windows异常处理机制。不同的是&#xff0c;有VC编译器(vcruntime.dll&#xff09;接管的部分&#xff0c;被CLR里面的函数ProcessCLRExcept…

Codeforces936C. Lock Puzzle

给个串&#xff0c;只能用操作shift x表示把后面x个字符翻转后放到串的前面。问s串怎么操作能变t串。n<2000&#xff0c;操作次数<6100。 打VP时这转来转去的有点晕。。。 可以想一种逐步构造的方法&#xff0c;即从一个小的完成构造的部分通过一顿操作&#xff0c;在不影…

公共服务领域英文译写规范_公共领域日:对版权和公共领域重要性的思考

公共服务领域英文译写规范The first of the year is Public Domain Day, a day intended to call attention to copyright issues and the public domain. At the Center for the Study of the Public Domain they have an interesting (and sobering) review of works that wo…

接入amazon avs_每日新闻综述:亚马逊将互联网接入推向全球的宏伟计划

接入amazon avsPlus Snap’s big push to stay relevant, Amazon’s Alexa-powered AirPods alternatives, more Android Q news, and a lot more. It’s time to talk about the biggest, coolest, or generally most interesting stories from the last 24 hours. 加上Snap保…

键盘忍者:使用单个热键弹出Vista日历

We’ve covered how to access the Windows Vista Calendar using the keyboard, but what if you wanted to assign a single keystroke to pop up the calendar? Yeah, sure, you can just click it with the mouse, but where’s the geek fun in that? 我们已经介绍了如何…

如何使用必应地图 WPF 控件

如何使用必应地图 WPF 控件如何使用必应地图 WPF 控件作者&#xff1a;WPFDevelopersOrg - 驚鏵原文链接&#xff1a;https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用.NET40&#xff1b;Visual Studio 2019;Bing Maps WPF 控件需要 .NET Framework 4.0和 Windows S…

如何保存推特链接以供以后从台式机和手机阅读

Have you come across a lot of interesting links from Twitter, but you don’t have the time to read all of them? Today we’ll show you how to read these links later from your desktop and phone. 您是否遇到过Twitter上很多有趣的链接&#xff0c;但没有时间阅读所…

【重大更新】DevExpress v17.2新版亮点—Bootstrap篇(二)

用户界面套包DevExpress v17.2日前终于正式发布&#xff0c;本站将以连载的形式为大家介绍各版本新增内容。本文将介绍了Bootstrap Controls v17.2 的CardView、Charts、Editors、GridView、Layout等新功能&#xff0c;快来下载试用新版本&#xff01; GridView Toolbar 在此版…

盘点 .NET 7 新功能

点击上方蓝字关注我们&#xff08;本文阅读时间&#xff1a;20分钟)本文翻译于 Jeremy Likness, Angelos Petropoulos 和 Jon Douglas 的博客.NET 7 为C# 11/F# 7、.NET MAUI、ASP.NET Core/Blazor、Web API、WinForms、WPF 等应用程序带来了更高的性能和新功能。使用 .NET 7&a…

nb-iot链路层加密_Google为低端Android手机和IoT设备创建了更快的加密

nb-iot链路层加密Google谷歌Low-resource Android phones and IoT devices don’t have the processing power to use modern encryption services, which makes them vulnerable to hacking. That’s why Google is introducing Adiantum, a super-fast encryption standard f…

MediatRPC - 基于MediatR和Quic通讯实现的RPC框架,比GRPC更简洁更低耦合,开源发布第一版...

大家好&#xff0c;我是失业在家&#xff0c;正在找工作的博主Jerry。作为一个.Net架构师&#xff0c;就要研究编程艺术&#xff0c;例如SOLID原则和各种设计模式。根据这些原则和实践&#xff0c;实现了一个更简洁更低耦合的RPC&#xff08;Remote Procedure Calls&#xff09…

wii拆机_设置防砖保护以保护和增强Wii

wii拆机We’ve shown you how to hack your Wii for homebrew software, emulators, and DVD playback, now it’s time to safeguard your Wii against bricking and fix some annoyances—like that stupid “Press A” health screen. 我们已经向您展示了如何破解Wii的自制软…

龙芯IPC追平Zen2 稳步推进产业生态

日前&#xff0c;2022年信息技术自主创新高峰论坛在南京成功召开&#xff0c;来自政府、产业、各行业领域的领导、专家学者、企业代表齐聚长江之滨&#xff0c;共话信息产业自主创新发展。铁流谈谈会上几个亮点。龙芯LA664追平AMD zen2相对于一些技术引进CPU在引进海外技术后CP…

摄像头水平视野垂直视野?_如何在“动物穿越:新视野”中的梦中游览某人的岛屿...

摄像头水平视野垂直视野?The promised second summer update for Animal Crossing: New Horizons has been released, and it restores the ability to visit another player’s island in your dreams. Before you can do so, though, you’ll need a Nintendo Online member…

中文版onlyoffice镜像制作

原文同步于&#xff1a;https://www.daxueyiwu.com/post/761 拉取5.4.2.46版本onlyoffice/documentserver镜像 docker pull onlyoffice/documentserver:5.4.2.46 该版本是支持20个连接数破解限制的最后一个版本&#xff0c;也是对中文字体界面显示中文不乱码支持比较好的一个版…