通過Dapr實現一個簡單的基於.net的微服務電商系統(二十)——Saga框架實現思路分享

  今天這篇博文的主要目的是分享一下我設計Saga的實現思路來拋磚引玉,其實Saga本身非常的類似於一個簡單的工作流體系,相比工作流不一樣的部分在於它沒有工作流的複雜邏輯處理機制(比如會籤),沒有條件分支機制,相對工作流不同的部分在於工作流流程阻塞結束後它多了一個反向補償的流程。同時相對於工作流通過靈活的配置來實現運行時來講他的邏輯流轉比較固化基本在代碼編寫階段就已經完成了流程的配置,編譯後運行時一般是不會更改的。下面就從配置、流轉、傳遞模型和異常處理幾個方面來講一下我的實現思路是什麼權當拋磚引玉,希望大家留言評論。

目錄:
一、通過Dapr實現一個簡單的基於.net的微服務電商系統

二、通過Dapr實現一個簡單的基於.net的微服務電商系統(二)——通訊框架講解

三、通過Dapr實現一個簡單的基於.net的微服務電商系統(三)——一步一步教你如何擼Dapr

四、通過Dapr實現一個簡單的基於.net的微服務電商系統(四)——一步一步教你如何擼Dapr之訂閱發佈

五、通過Dapr實現一個簡單的基於.net的微服務電商系統(五)——一步一步教你如何擼Dapr之狀態管理

六、通過Dapr實現一個簡單的基於.net的微服務電商系統(六)——一步一步教你如何擼Dapr之Actor服務

七、通過Dapr實現一個簡單的基於.net的微服務電商系統(七)——一步一步教你如何擼Dapr之服務限流

八、通過Dapr實現一個簡單的基於.net的微服務電商系統(八)——一步一步教你如何擼Dapr之鏈路追蹤

九、通過Dapr實現一個簡單的基於.net的微服務電商系統(九)——一步一步教你如何擼Dapr之OAuth2授權 && 百度版Oauth2

十、通過Dapr實現一個簡單的基於.net的微服務電商系統(十)——一步一步教你如何擼Dapr之綁定

十一、通過Dapr實現一個簡單的基於.net的微服務電商系統(十一)——一步一步教你如何擼Dapr之自動擴/縮容

十二、通過Dapr實現一個簡單的基於.net的微服務電商系統(十二)——istio+dapr構建多運行時服務網格

十三、通過Dapr實現一個簡單的基於.net的微服務電商系統(十三)——istio+dapr構建多運行時服務網格之生產環境部署

十四、通過Dapr實現一個簡單的基於.net的微服務電商系統(十四)——開發環境容器調試小技巧

十五、通過Dapr實現一個簡單的基於.net的微服務電商系統(十五)——集中式接口文檔實現

十六、通過Dapr實現一個簡單的基於.net的微服務電商系統(十六)——dapr+sentinel中間件實現服務保護

十七、通過Dapr實現一個簡單的基於.net的微服務電商系統(十七)——服務保護之動態配置與熱重載

十八、通過Dapr實現一個簡單的基於.net的微服務電商系統(十八)——服務保護之多級緩存

十九、通過Dapr實現一個簡單的基於.net的微服務電商系統(十九)——分佈式事務之Saga模式

二十、通過Dapr實現一個簡單的基於.net的微服務電商系統(二十)——Saga框架實現思路分享

附錄:(如果你覺得對你有用,請給個star)
一、電商Demo地址

二、通訊框架地址

三、Saga框架地址

 

一、整體流程

  首先我們通過一個講的比較爛的業務模型(庫存-餘額-訂單)來簡述一下saga是如何實現分佈式事務的。然後再講解一下saga實現這套流程都做了哪些工作來讓大家對分佈式事務有一個清晰的認知。首先來講一下爲什麼要實現一個分佈式事務。當我們的應用通過業務拆解後以物理隔離的模式運行在不同的物理機/虛擬機/容器上並且我們的數據庫也做了相應的隔離後,我們沒有辦法通過發佈一個單一的原子的ACID事務來達到事務的一致性。單個數據庫在運行事務時通過對應的機制(諸如主流的MVCC多版本併發控制)來確保ACID。但是在跨多個數據庫的情況下,各家數據庫廠商就只能通過二階段提交的XA協議來實現分佈式事務。這種方案確實在一定程度上降低了分佈式事務的複雜性不過性能上卻無法做到很好的平衡。而更加常見的辦法是通過應用自身調用各自的數據庫原子事務以鏈條的形式來完成分佈式事務,而saga作爲其中的一種方案已經在各大企業的業務場景中被廣泛的實踐過了。

  下面我們就看看一個比較典型電商下單的場景,當電商註冊用戶在電商平臺完成內用金充值後可以直接在平臺上下單購買商品。下單的流程如下:點擊購物車下訂單->預扣庫存->預扣內用金->創建訂單,流程如下:

   在理想情況下當我們下訂單順利時,每一個服務只需要包裹各自的事務(扣庫存/扣餘額/創建訂單),通過調用鏈即可完成一個完整的訂單創建業務。然而由於數據庫存在物理隔離,很多時候我們需要面對這種情況:

  這個時候saga的作用就體現出來了,它可以通過事件訂閱/發佈的形式幫你完成1-8的自動化的調用或者補償調用,你需要做的只是一個配置和把對應的訂閱/補償方法給註冊到這個配置的主題上即可。接下來我們就講講saga是如何一步一步來實現這套邏輯的。

二、配置

  *下面的所有實現均以Dapr實現集成爲例,即(Oxygen-Saga.PubSub.Dapr + Oxygen-Saga.Store.Dapr),但是整體集成邏輯和(Oxygen-Saga.PubSub.Rabbitmq + Oxygen-Saga.Store.Redis) 區別不大,只有部分隊列和持久化實現的區別。

  首先我們需要一個管理所有分佈式事務配置的管理包項目,這個包會作爲一個通用的nuget or 項目被其他具體的業務服務繼承。然後所有的Saga配置都應該在這個包中編寫對應的配置文件。同時每個服務會引入這個包並且創建自己的SagaConfiguration配置文件用於向saga服務申明自己需要創建Saga服務

三、註冊

  註冊的過程主要分爲三個部分,一個部分就是需要把之前Topic配置文件引入到本地並形成我們的Saga配置,一部分是引入我們需要的第三方的組件來支持整個saga運行起來,另一部分是創建對應的訂閱服務代理來支持對事件的訂閱和分發。

四、流轉

  當整個註冊工作完成後啓動項目會按照上圖所示進行相關的註冊和構造代理的工作,接下來就是具體業務的流轉過程,所有的訂閱會被一個SagaSubscribe(由上一步的RegisterSagaHandler觸發並注入ISagaEventHandler後創建所得)接受到,並通過該Hub完成對應的路由投遞到具體的方法函數進行業務流轉。整個Hub結構如下所示,可以看到其實Saga是在每個服務註冊了一個訂閱器,通過訂閱器接收到其他業務發佈的Saga事件後通過代理的方式路由到具體的業務實現上來實現流程代理的。

  那Saga又是怎麼實現自動化的發佈下一個事件或者根據代理調用目標函數拋出異常後自動進行補償事件的呢,這就要看Saga的傳遞模型結構了,整個模型結構如下圖,可以看到這裏的Topic其實就是對應到的上圖的TopicName,所以當Proxy收到事件解析出Topic後即可路由到具體的函數進行處理,當函數處理完畢後,Proxy即可根據鏈表當前所在的節點獲取Next,如果Next不爲空,則發送當前函數回調的Result封裝成一個消息包投遞到Next所屬的服務,如果Next爲空則說明整個流程結束,不再執行任何操作。

五、異常

  同樣的由於不能確保所有的業務能夠完完全全的處理完畢自己業務比如當庫存不足時,餘額不足時,這時候就需要業務端自行通過觸發異常的方式回調上一步的補償,這樣代理就會嘗試從鏈表中獲取Previous,如果Previous不爲空,則發送當前函數拋出異常(SagaException<T>)回調的Result封裝成一個消息包投遞到Previous所屬的服務,如果Previous爲空則說明整個補償流程結束,不再執行任何操作。不過還有一種情況,就是當前目標函數觸發的是非SagaException異常,這種情況下Saga沒有辦法獲取到上一步補償所需的數據,所以這個時候就只能交給人工處理,也就是在註冊部分第三步RegisterSagaHandler可以注入一個異常處理程序,當發生異常Saga無法處理時我們會嘗試向Func<IServiceProvider, ErrorModel, Task> errorHandle 函數投遞ErrorModel,由客戶端自行決定如何處理異常。

六、持久化

   所有的消息在流轉過程中會被包裝爲一個SagaData類型持久化到Store中,並根據流轉類型被賦予三個狀態,分別是Processing,Done,Error。這部分數據會在Store中保存24小時。未來擴展中會提供相應的接口獲取這部分數據用於框架擴展。

 

好了,整個Saga的設計思路就到此講解完畢了。在設計中肯定還有很多不足和有缺陷的部分,希望小夥伴們留言評論。最後再次附上系列開源地址,歡迎star fork issues 一鍵三連

一、電商Demo地址

二、通訊框架地址

三、Saga框架地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章