国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
多研究些架構,少談些框架( 3 ):事件驅動架構
原文出處: JoeCao

接上篇,我們采用了領域驅動的開發(fā)方式,使用了充血模型,享受了他的好處,但是也不得不面對他帶來的弊端。這個弊端在分布式的微服務架構下面又被放大。

事務一致性

事務一致性的問題在Monolithic下面不是大問題,在微服務下面卻是很致命,我們回顧一下所謂的ACID原則

  • Atomicity – 原子性,改變數(shù)據(jù)狀態(tài)要么是一起完成,要么一起失敗
  • Consistency – 一致性,數(shù)據(jù)的狀態(tài)是完整一致的
  • Isolation – 隔離線,即使有并發(fā)事務,互相之間也不影響
  • Durability – 持久性, 一旦事務提交,不可撤銷

在單體服務和關系型數(shù)據(jù)庫的時候,我們很容易通過數(shù)據(jù)庫的特性去完成ACID。但是一旦你按照DDD拆分聚合根-微服務架構,他們的數(shù)據(jù)庫就已經(jīng)分離開了,你就要獨立面對分布式事務,要在自己的代碼里面滿足ACID。
對于分布式事務,大家一般會想到以前的JTA標準,2PC兩段式提交。我記得當年在Dubbo群里面,基本每周都會有人詢問Dubbo啥時候支撐分布式事務。實際上根據(jù)分布式系統(tǒng)中CAP原則,當P(分區(qū)容忍)發(fā)生的時候,強行追求C(一致性),會導致(A)可用性、吞吐量下降,此時我們一般用最終一致性來保證我們系統(tǒng)的AP能力。當然不是說放棄C,而是在一般情況下CAP都能保證,在發(fā)生分區(qū)的情況下,我們可以通過最終一致性來保證數(shù)據(jù)一致。

例:
在電商業(yè)務的下訂單凍結庫存場景。需要根據(jù)庫存情況確定訂單是否成交。
假設你已經(jīng)采用了分布式系統(tǒng),這里訂單模塊和庫存模塊是兩個服務,分別擁有自己的存儲(關系型數(shù)據(jù)庫),

在一個數(shù)據(jù)庫的時候,一個事務就能搞定兩張表的修改,但是微服務中,就沒法這么做了。
在DDD理念中,一次事務只能改變一個聚合內部的狀態(tài),如果多個聚合之間需要狀態(tài)一致,那么就要通過最終一致性。訂單和庫存明顯是分屬于兩個不同的限界上下文的聚合,這里需要實現(xiàn)最終一致性,就需要使用事件驅動的架構。

事件驅動實現(xiàn)最終一致性

事件驅動架構在領域對象之間通過異步的消息來同步狀態(tài),有些消息也可以同時發(fā)布給多個服務,在消息引起了一個服務的同步后可能會引起另外消息,事件會擴散開。嚴格意義上的事件驅動是沒有同步調用的。

例子:
在訂單服務新增訂單后,訂單的狀態(tài)是“已開啟”,然后發(fā)布一個Order Created事件到消息隊列上

庫存服務在接收到Order Created 事件后,將庫存表格中的某sku減掉可銷售庫存,增加訂單占用庫存,然后再發(fā)送一個Inventory Locked事件給消息隊列

訂單服務接收到Inventory Locked事件,將訂單的狀態(tài)改為“已確認”

有人問,如果庫存不足,鎖定不成功怎么辦? 簡單,庫存服務發(fā)送一個Lock Fail事件, 訂單服務接收后,把訂單置為“已取消”。

好消息,我們可以不用鎖!事件驅動有個很大的優(yōu)勢就是取消了并發(fā),所有請求都是排隊進來,這對我們實施充血模型有很大幫助,我們可以不需要自己來管理內存中的鎖了。取消鎖,隊列處理效率很高,事件驅動可以用在高并發(fā)場景下,比如搶購。

是的,用戶體驗有改變,用了這個事件驅動,用戶的體驗有可能會有改變,比如原來同步架構的時候沒有庫存,就馬上告訴你條件不滿足無法下單,不會生成訂單;但是改了事件機制,訂單是立即生成的,很可能過了一會系統(tǒng)通知你訂單被取消掉。 就像搶購“小米手機”一樣,幾十萬人在排隊,排了很久告訴你沒貨了,明天再來吧。如果希望用戶立即得到結果,可以在前端想辦法,在BFF(Backend For Frontend)使用CountDownLatch這樣的鎖把后端的異步轉成前端同步,當然這樣BFF消耗比較大。

沒辦法,產(chǎn)品經(jīng)理不接受,產(chǎn)品經(jīng)理說用戶的體驗必須是沒有庫存就不會生成訂單,這個方案會不斷的生成取消的訂單,他不能接受,怎么辦?那就在訂單列表查詢的時候,略過這些cancel狀態(tài)的訂單吧,也許需要一個額外的視圖來做。我并不是一個理想主義者,解決當前的問題是我首先要考慮的,我們設計微服務的目的是本想是解決業(yè)務并發(fā)量。而現(xiàn)在面臨的卻是用戶體驗的問題,所以架構設計也是需要妥協(xié)的:( 但是至少分析完了,我知道我妥協(xié)在什么地方,為什么妥協(xié),未來還有可能改變。

多個領域多表Join查詢

  • 我個人認為聚合根這樣的模式對修改狀態(tài)是特別合適,但是對搜索數(shù)據(jù)的確是不方便,比如篩選出一批符合條件的訂單這樣的需求,本身聚合根對象不能承擔批量的查詢任務,因為這不是他的職責。那就必須依賴“領域服務(Domain Service)”這種設施。

當一個方法不便放在實體或者值對象上,使用領域服務便是最佳的解決方法,請確保領域服務是無狀態(tài)的。

  • 我們的查詢任務往往很復雜,比如查詢商品列表,要求按照上個月的銷售額進行排序; 要按照商品的退貨率排序等等。但是在微服務和DDD之后,我們的存儲模型已經(jīng)被拆離開,上述的查詢都是要涉及訂單、用戶、商品多個領域的數(shù)據(jù)。如何搞? 此時我們要引入一個視圖的概念。比如下面的,查詢用戶名下訂單的操作,直接調用兩個服務自己在內存中join效率無疑是很低的,再加上一些filter條件、分頁,沒法做了。于是我們將事件廣播出去,由一個單獨的視圖服務來接收這些事件,并形成一個物化視圖(materialized view),這些數(shù)據(jù)已經(jīng)join過,處理過,放在一個單獨的查詢庫中,等待查詢,這是一個典型的以空間換時間的處理方式。

經(jīng)過分析,除了簡單的根據(jù)主鍵Find或者沒有太多關聯(lián)的List查詢,我們大部分的查詢任務可以放到單獨的查詢庫中,這個查詢庫可以是關系數(shù)據(jù)庫的ReadOnly庫,也可以是NoSQL的數(shù)據(jù)庫,實際上我們在項目中使用了ElasticSearch作為專門的查詢視圖,效果很不錯

限界上下文(Bounded Context)和數(shù)據(jù)耦合

除了多領域join的問題,我們在業(yè)務中還會經(jīng)常碰到一些場景,比如電商中的商品信息是基礎信息,屬于單獨的BC,而其他BC,不管是營銷服務、價格服務、購物車服務、訂單服務都是需要引用這個商品信息的。但是需要的商品信息只是全部的一小部分而已,營銷服務需要商品的id和名稱、上下架狀態(tài);訂單服務需要商品id、名稱、目錄、價格等等。這比起商品中心定義一個商品(商品id、名稱、規(guī)格、規(guī)格值、詳情等等)只是一個很小的子集。這說明不同的限界上下文的同樣的術語,但是所指的概念不一樣。 這樣的問題映射到我們的實現(xiàn)中,每次在訂單、營銷模塊中直接查詢商品模塊,肯定是不合適,因為

  • 商品中心需要適配每個服務需要的數(shù)據(jù),提供不同的接口
  • 并發(fā)量必然很大
  • 服務之間的耦合嚴重,一旦宕機、升級影響的范圍很大。

特別是最后一條,嚴重限制了我們獲得微服務提供的優(yōu)勢“松耦合、每個服務自己可以頻繁升級不影響其他模塊”。這就需要我們通過事件驅動方法,適當冗余一些數(shù)據(jù)到不同的BC去,把這種耦合拆解開。這種耦合有時候是通過Value Object嵌入到實體中的方式,在生成實體的時候就冗余,比如訂單在生成的時候就冗余了商品的信息;有時候是通過額外的Value Object列表方式,營銷中心冗余一部分相關的商品列表數(shù)據(jù),并隨時關注監(jiān)聽商品的上下級狀態(tài),同步替換掉本限界上下文的商品列表。

下圖一個下單場景分析,在電商系統(tǒng)中,我們可以認為會員和商品是所有業(yè)務的基礎數(shù)據(jù),他們的變更應該是通過廣播的方式發(fā)布到各個領域,每個領域保留自己需要的信息。

保證最終一致性

最終一致性成功依賴很多條件

  • 依賴消息傳遞的可靠性,可能A系統(tǒng)變更了狀態(tài),消息發(fā)到B系統(tǒng)的時候丟失了,導致AB的狀態(tài)不一致
  • 依賴服務的可靠性,如果A系統(tǒng)變更了自己的狀態(tài),但是還沒來得及發(fā)送消息就掛了。也會導致狀態(tài)不一致

我記得JavaEE規(guī)范中的JMS中有針對這兩種問題的處理要求,一個是JMS通過各種確認消息(Client Acknowledge等)來保證消息的投遞可靠性,另外是JMS的消息投遞操作可以加入到數(shù)據(jù)庫的事務中-即沒有發(fā)送消息,會引起數(shù)據(jù)庫的回滾(沒有查資料,不是很準確的描述,請專家指正)。不過現(xiàn)在符合JMS規(guī)范的MQ沒幾個,特別是保一致性需要降低性能,現(xiàn)在標榜高吞吐量的MQ都把問題拋給了我們自己的應用解決。所以這里介紹幾個常見的方法,來提升最終一致性的效果。

使用本地事務

還是以上面的訂單扣取信用的例子

  • 訂單服務開啟本地事務,首先新增訂單;
  • 然后將Order Created事件插入一張專門Event表,事務提交;
  • 有一個單獨的定時任務線程,定期掃描Event表,掃出來需要發(fā)送的就丟到MQ,同時把Event設置為“已發(fā)送”。

方案的優(yōu)勢是使用了本地數(shù)據(jù)庫的事務,如果Event沒有插入成功,那么訂單也不會被創(chuàng)建;線程掃描后把event置為已發(fā)送,也確保了消息不會被漏發(fā)(我們的目標是寧可重發(fā),也不要漏發(fā),因為Event處理會被設計為冪等)。
缺點是需要單獨處理Event發(fā)布在業(yè)務邏輯中,繁瑣容易忘記;Event發(fā)送有些滯后;定時掃描性能消耗大,而且會產(chǎn)生數(shù)據(jù)庫高水位隱患;

我們稍作改進,使用數(shù)據(jù)庫特有的MySQL Binlog跟蹤(阿里的Canal)或者Oracle的GoldenGate技術可以獲得數(shù)據(jù)庫的Event表的變更通知,這樣就可以避免通過定時任務來掃描了

不過用了這些數(shù)據(jù)庫日志的工具,會和具體的數(shù)據(jù)庫實現(xiàn)(甚至是特定的版本)綁定,決策的時候請慎重。

使用Event Sourcing 事件溯源

事件溯源對我們來說是一個特別的思路,他并不持久化Entity對象,而是只把初始狀態(tài)和每次變更的Event記錄下來,并在內存中根據(jù)Event還原Entity對象的最新狀態(tài),具體實現(xiàn)很類似數(shù)據(jù)庫的Redolog的實現(xiàn),只是他把這種機制放到了應用層來。

雖然事件溯源有很多宣稱的優(yōu)勢,引入這種技術要特別小心,首先他不一定適合大部分的業(yè)務場景,一旦變更很多的情況下,效率的確是個大問題;另外一些查詢的問題也是困擾。

我們僅僅在個別的業(yè)務上探索性的使用Event Souring和AxonFramework,由于實現(xiàn)起來比較復雜,具體的情況還需要等到實踐一段時間后再來總結,也許需要額外的一篇文章來詳細描述

以上是對事件驅動在微服務架構中一些我的理解,文章部分借鑒了Chris Richardson的Blog,https://www.nginx.com/blog/event-driven-data-management-microservices/ 在此我向他表示致謝 。

本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
多研究些架構,少談些框架——一名阿里架構師的微服務筆記
微服務架構-利用事件驅動實現(xiàn)最終一致性
真是絕了,做了這么多年程序員第一次搞懂微服務架構的數(shù)據(jù)一致性
微服務架構下的數(shù)據(jù)一致性保證(一)
dW 編輯推薦:企業(yè)數(shù)據(jù)訪問模式
實踐丨分布式事務解決方案匯總:2PC、消息中間件、TCC、狀態(tài)機 重試 冪等
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服