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

a1010發表於2022-02-22

  今天這篇博文的主要目的是分享一下我設計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框架地址

相關文章