重視和解決 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.pswp.cn/news/281178.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/281178.shtml
英文地址,請注明出處:http://en.pswp.cn/news/281178.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

個人博客建站方案推薦

1.服務器選擇 正值雙十一來臨之際各大服務器提供商又大量的優惠活動&#xff0c;各位要步入個人站長行列的小哥們時機要把握好了&#xff0c;我個人使用過阿里云的服務器&#xff0c;騰訊云的服務器&#xff0c;華為云的服務器。其實&#xff0c;個人感覺就放個博客&#xff0c…

linux系統下nginx安裝目錄和nginx.conf配置文件目錄

linux系統下nginx安裝目錄和nginx.conf配置文件目錄 1、查看nginx安裝目錄 輸入命令 # ps -ef | grep nginx 返回結果包含安裝目錄 root 2662 1 0 07:12 ? 00:00:00 nginx: master process /usr/sbin/nginx 2、查看nginx.conf配置文件目錄 輸入命令 # nginx…

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…

Vijos p1484 ISBN號碼

描述每一本正式出版的圖書都有一個ISBN號碼與之對應&#xff0c;ISBN碼包括9位數字、1位識別碼和3位分隔符&#xff0c;其規定格式如“x-xxx-xxxxx-x”&#xff0c;其中符號“-”就是分隔符&#xff08;鍵盤上的減號&#xff09;&#xff0c;最后一位是識別碼&#xff0c;例如0…

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…

Elasticsearch 實戰經驗總結

Centos7下es 7.7.0安裝配置 怎么安裝使用elasticsearch-head插件 用logstash同步Mysql數據到ES Springboot使用ES官方推薦方式REST Client整合ES實現關鍵詞高亮 ELK-Elasticsearch&#xff0c;Logstash&#xff0c;kibana搭建基于日志文件的日志分析系統 設置elasticsearc…

.Net 7 的 AOT 和 CLR有什么區別?

楔子&#xff1a;AOT和 CLR的區別是什么呢&#xff1f;大部分人肯定會說&#xff0c;一個編譯成本地機器碼&#xff08;Native Code&#xff09;&#xff0c;一個是JIT即時編譯的結果。這么說&#xff0c;其實也對&#xff0c;但是不具體。具體應該怎么看呢&#xff1f;AOTAOT實…

接入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保…

計算的未來

我自己倒是后來也是覺得我自己可以想象一個未來的技術&#xff0c;就是以后的編程的語言和庫可以抽象現在的一些高級語言的關鍵字。比如要寫一個編輯器的時候&#xff0c;只要給點這些東西的數據結構和數據流向&#xff0c;而一些什么很繁瑣的一些底層編碼都是可以用高級語言來…

nginx 實用配置問題總結

配置 tomcat&#xff0c;nginx&#xff0c;解決 post 請求超時問題nginx 跨域問題 CORS policy: No Access-Control-Allow-Originnginx 配置靜態驗證文件&#xff0c;報 404&#xff0c;解決方案nginx 獲取用戶真實 IPcentos 部署 php 網站方法-使用 nginx ssl https

零部件分類屬性

離散制造業的研發、生產跟產品零部件緊密聯系在一起&#xff0c;從企業業務流程來說零部件涉及研發、采購、倉儲、生產、質量、售后和配件等多個部門&#xff0c;為了更好地管理零部件&#xff0c;下面我們一起來看看零部件概念及分類。1、按行業屬性分類&#xff08;1&#xf…

鍵盤忍者:使用單個熱鍵彈出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? 我們已經介紹了如何…

Linux下全局安裝composer方法

# 下載composer [vagrantlocalhost ~]$ curl -sS https://getcomposer.org/installer | php# 將composer.phar文件移動到bin目錄以便全局使用composer命令 [vagrantlocalhost ~]$ mv composer.phar /usr/local/bin/composer# 切換國內源 [vagrantlocalhost ~]$ composer config…

如何使用必應地圖 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;但沒有時間閱讀所…