1. 程式人生 > >微服務(多庫、跨伺服器)分散式事務元件設計思考(王曉龍,2019-12-10)

微服務(多庫、跨伺服器)分散式事務元件設計思考(王曉龍,2019-12-10)

事務:

一、可能同時存在不可預知數量的事務;

二、需要協調所有相關事務並行;

三、事務可能分佈在不同的伺服器上;

四、通過本地事務結合,實施分散式事務;(結合Redis釋出訂閱機制、結合介面入參,Redis用於做跨伺服器釋出訂閱,通過訊息傳遞實現對整體服務的最終要麼提交、要麼集體回滾的控制)

---------------------------------------------------

一、事務訂閱訊息約定:

  事務分為發起方、關聯方;

  事務發起方,負責建立分散式事務(同時產生一個唯一事務ID),同時自動監聽當前分散式事務的訂閱訊息,主題為:DistributedTransaction_StatusReport_TransactionID

  該主題的訊息分為兩個引數,一個是RequestID、一個是TransactionStatus,表示目標服務的執行狀態。

  在分散式事務內呼叫其它服務時,同時將事務ID傳遞過去,同時為每個不同服務的請求產生一個RequestID;

  (微服務中的服務API請求基類需要為分散式事務預留事務ID欄位,預設為null或空,需要以事務形式執行時傳入,或者特定接口才實現分散式事務,則為介面新增事務ID專用引數)

      其它服務在執行時,判斷有事務ID,則將操作基於事務的情形排程,但不執行提交,在提交前,做WaitOne和超時等待;

      等待前啟動Redis訂閱,訂閱該事務ID關聯的訊息,訊息為:DistributedTransaction_StatusCommit_TransactionID

      監聽到該訊息時,做Set操作,使超時退出,並完成事務操作;

  Redis訊息會有三種結果:

    訊息超時:回滾

    訊息提交:提交

    訊息回滾:回滾

    【服務不論執行回滾還是提交,都需要解除對上面事務訊息的訂閱;】

   分散式事務建立方,負責記錄各個服務呼叫的結果,一發現有服務呼叫階段失敗、出錯、沒有完成,即時對前面的其它服務傳送回滾通知,後續服務不再繼續呼叫;

    建立方在全部服務、自身事務都執行後,確認沒有問題後,向各個分散式機器推送訊息,通知執行提交。

  ----------------------------------------------

  至此分散式事務完成。解決多個微服務在服務間呼叫時,各個本地事務協調的問題。

二、設計:

  DistributedTransaction.Create() //產生一個分散式事務ID,或者考慮使用Using寫法事務範圍內,分佈呼叫其它服務,並檢查每個服務的排程結果,

  DistributedTransaction.SessionID //儲存上面呼叫Create建立的事務ID

  DistributedTransaction.Commit() //執行分散式事務的提交,事實就是向Redis釋出提交訊息,向全部訂閱中的裝置,傳送訂閱訊息;

  DistributedTransaction.Rollback() //執行分散式事務的回滾。同樣是向Redis釋出回滾訊息。

  DistributedTransaction.AddServiceTransaction({ServiceRequestID、TransactionStatus}) //新增一個事務關聯方,用於本地記錄有幾個服務參與了事務,並且記錄對方的狀態(等待中、已提交、已回滾),也表示需要為每個被請求的服務關聯一個請求ID,後續服務若發生回滾或提交,可以向建立方傳送訊息表明狀態。

  DistributedTransaction.HasRollbacks() //用於判斷是否有服務事務發生回滾

  DistributedTransaction.HasCommits() //用於判斷是否有事務發生提交;

  DistributedTransaction.WaitComplate()

  //用於判斷是否全部提交成功,只有全部狀態為已提交時,視為全部提交,如果有提交有回滾,則事務有問題,視為false,如果有等待,有提交,則等待最終完成(也需要有超時機制)。

  //該方法會等待最終結果,並且,返回事務的最終結果及描述資訊,但對於失敗的部份,僅能記錄請求ID,可以通過擴充套件,完成對請求ID和服務本地關聯;


三、問題:

  1、事務的跟蹤,上述沒有跟蹤機制,如果,假定有三個不同的服務參與事務,其中三個都執行完成,事務處於待提交或回滾狀態,在釋出提交訊息前,

    其中一個服務A當機(假定斷電),無法接收到訊息,另外兩個服務成功接收到訊息,此時另外兩個服務執行提交,服務A由於當機,沒有提交;

    理論上,由於事務不參與業務,不跟蹤具體影響的表、影響前後的資料,因此,無法執行人工回滾;

    但理論上可以增加回滾或提交、服務節點增加等機制來確保 分散式事務發起方,清楚是否全部完成,還是部份完成提交。

    但若假設事務發起發當機?本機事務尚未提交,但其它三個服務的事務均已提交?本機恢復後理論上應該執行本地事務的提交。

  2、超時時間的問題

    服務等待接收來自Redis的提交或回滾指令,在等待階段,為了實現自動回滾,因此會進行超時監控,超時時間應該是比較短的時間,但由於不確定後續

    事務建立方還有多少個服務需要呼叫,且不能確定每個服務估計的執行時長,較短的時間有可能引發不正常回滾。

    如果可能,應該考慮多個服務並行化執行,確保合併相同的時間,等待的時間為最耗時服務的執行時間;任何事務都應該控制在儘可能短的時間內完成。

  2.1、超時引發回滾時

    上述未設計由於某服務等待超時自動回滾後,通知到建立方,或者關聯服務批量執行回滾,另外,如果由發生回滾的服務傳送通知。

四、解決的問題:

    上述機制,可以解決非異常情況下的分散式、微服務事務的同步執行或同步回滾,前提假設:不存在伺服器突然當機、Redis當機的情況下。

 

----------------------------------------------------

下面再來一段EF/LINQ2SQL跨庫事務程式碼,實現原理和上面的原理相似,只是沒有跨伺服器的場景,都是本地事務:未經過封裝,僅為實驗性程式碼,如何需要在生產環境中使用,建議寫一個類做封裝,方便集中控制,無差別控制,不區分需要跨庫的資料庫數量,實現出錯時自動對已完成(未提交)的事務進行集中回滾。

 var dc = new DataContexts(0);
        dc.Ztb_shopdict.Connection.Open();
        var transaction1 = dc.Ztb_shopdict.Connection.BeginTransaction();
        dc.Ztb_shopinfo.Connection.Open();
        var tran2 = dc.Ztb_shopinfo.Connection.BeginTransaction();

        //事務是否成功的標記
        bool TransactionStatus = true;
        try
        {
            dc.Ztb_shopdict.Transaction = transaction1;
            dc.Ztb_shopdict.Dict_SystemPara.InsertOnSubmit(new API.Domain.ztb_shopdict.Dict_SystemPara() { ShopID = 0, ParaKey = "a", ParaValue = "v" });
            dc.Ztb_shopdict.SubmitChanges();
            try
            {
                dc.Ztb_shopinfo.Transaction = tran2;
                dc.Ztb_shopinfo.Activity_Item.InsertOnSubmit(new API.Domain.ztb_shopinfo.Activity_Item() { Activity_Info_ID = 0, Activity_Price = 0, addActivity_Price = 0, enable = true, firstActivity_Price = 0, levelId = 0, ServiceID = 0, ShopID = 0 });
                dc.Ztb_shopinfo.SubmitChanges();
            }
            catch (Exception ex)
            {
                tran2.Rollback();
                //同時丟擲錯誤,引起1回滾
                throw ex;
            }
        }
        catch (Exception ex)
        {
            TransactionStatus = false;
            transaction1.Rollback();
            Response.Write(ex.ToString());
        }

        if (TransactionStatus)
        {
            //所有事務都成功了,一起提交
                transaction1.Commit();
                tran2.Commit();
        }

  

&n