使用“消息服務框架”(MSF)實現分散式事務的三階段提交協議(電商創建訂單的示例)

来源:https://www.cnblogs.com/bluedoctor/archive/2018/01/31/8384948.html
-Advertisement-
Play Games

使用消息服務框架,實現一個3階段提交協議的分散式事務,支持多種資料庫,甚至不支持分散式事務的嵌入式資料庫也可以實現分散式事務的功能,方便在微服務架構下實現分散式事務。 ...


1,示例解決方案介紹

在上一篇 《消息服務框架(MSF)應用實例之分散式事務三階段提交協議的實現》中,我們分析了分散式事務的三階段提交協議的原理,現在我們來看看如何使用消息服務框架(MSF)來具體實現並且看用它來實現的一些優勢。

首先,從Github克隆項目源碼,地址:https://github.com/bluedoctor/MSF-DistTransExample

解決方案如下圖:

我們看到解決方案有4個項目:

  1. DistTransClient:分散式事務示例的客戶端,它調用“訂單服務”,創建一個訂單,服務會返回創建結果是成功還是失敗;
  2. DistTransDto:包含商品,訂單和訂單詳情的實體類型介面以及相關的介面實現;
  3. DistTransServices:包含訂單服務,商品服務和分散式事務控制器服務;
  4. TistTransApp:本測試的宿主程式項目,主要用於安裝消息服務框架的服務宿主程式,以及啟動訂單,商品和分散式事務控制器的服務進程。

 2,創建訂單的業務簡介

2.1,基本概念

下麵先介紹本示例要解決的業務,並通過這個業務來分析分散式事務的執行過程。

在本示例中,使用的是電商系統最常見的業務場景:下單業務,它的業務流程也概括起來比較簡單:

創建訂單:

  1. 生成訂單基本信息;
  2. 生成訂單項目明細(已購商品清單):
    1. 檢查庫存是否足夠
    2. 扣減庫存

 當然,在具體的電商業務系統中,下單業務比較複雜,特別是對庫存的扣減方式,但大體的業務流程就是這樣的,我們今天的重點是研究這個下單過程在分散式環境下如何實現。

2.2,微服務架構

假設我們的電商平臺使用微服務架構的,包含了用戶服務,商品服務,訂單服務和支付服務,這4個服務在下單業務中的功能分別如下:

  • 用戶服務:檢查當前用戶是否有效,查詢用戶的相關信息,比如用戶姓名,聯繫電話等;
  • 訂單服務:生成訂單,包括結合用戶服務的用戶信息,生成訂單基本信息;結合商品服務,生成訂單項目明細;
  • 商品服務:向訂單服務返回商品的相關信息,並返回庫存是否可用,如果可用就扣減庫存;
  • 支付服務:由第三方提供,但參與創建訂單的流程,用戶下單後需要用戶去第三方支付系統完成支付,然後支付服務回調訂單服務,完成有效訂單確認。

 下麵是這4個服務在創建訂單的業務流程圖:

上圖中,支付服務是第三方提供的服務,需要用戶在創建訂單後跳轉調用,所以本質上不是訂單服務直接調用,訂單服務需要提供一個支付完成的回調通知介面,完成有效訂單的確認。 而用戶服務作為服務調用的發起方,它會傳遞必要的信息給訂單服務,因此,對於“創建訂單”這個具體的業務功能,它涉及的需要同時進行操作的只有創建訂單和扣減庫存這兩個子業務,並且要求這2個子業務操作具有原子性,即要麼同時成功,要麼同時失敗撤銷,所以這兩個操作組成一個事務操作,在我們當前的場景中,它是一個分散式事務。

2.3,分散式事務中的微服務容器

在本例中,我們使用消息服務框架(MSF)來實現分散式事務,為了更加真實的模擬微服務架構,我們將創建訂單相關的服務劃分為3個獨立的進程,這些進程就是MSF.Host服務容器,這裡分為3個服務容器:

  • 協調器服務容器:運行分散式事務協調器服務;
  • 訂單服務容器:運行訂單服務和分散式事務控制器組件;
  • 商品服務容器:運行商品服務和分散式事務控制器組件。

下麵是這3個服務容器的進程調用關係圖:

 

 

3,創建訂單的分散式事務流程

下麵來看創建訂單的分散式事務處理過程,為簡單起見,只討論正常的流程,其中異常的流程,請參考原文對於3階段提供分散式事務的具體原理。

1,客戶端調用訂單服務的創建訂單方法;(上圖步驟1)

2,訂單服務實例化,接受一個訂單號,用戶號,要購買的商品清單3個參數來創建訂單;(上圖步驟1)

3,創建訂單的方法向分散式事務控制器進行本地事務註冊,傳入創建訂單的事務方法(委托);(上圖步驟2)

4,創建訂單的事務方法遠程調用商品服務,更新商品庫存;(上圖步驟3)

5,商品服務的更新商品庫存方法向分散式事務控制器進行本地事務註冊,傳入具體更新庫存的事務方法(委托);(上圖步驟4)

6,商品服務執行完成更新庫存的方法,向訂單服務返回必要的信息,準備好提交事務;(上圖步驟5)

7,訂單服務收到商品服務的返回信息,構建好訂單和訂單明細,準備好提交事務;(上圖步驟6)

8,分散式事務控制器檢測到註冊的各事務資源伺服器(商品服務和訂單服務)都已經準備好提交事務,向它們發出提交指令;

9,商品服務和訂單服務收到提交指令,提交本地事務,事務資源服務方法執行完成;(上圖步驟7,8)

10,分散式事務控制器收到事務資源伺服器的反饋,登記本次分散式事務執行完成;

11,訂單服務標記創建訂單成功,向客戶端返回信息。

4,分散式事務服務和組件

4.1,分散式事務控制器

分散式事務控制器是提供給事務資源服務使用的組件,在本示例中是類 DTController,它提供瞭如下重要方法:

  • 檢查並開啟一個分散式事務控制器對象
  • 移除一個事務控制器
  • 累計事務資源伺服器
  • 獲取分散式事務的狀態
  • 3階段分散式事務請求函數
  • 提交事務的方法
  • 回滾事務的方法

其中“3階段分散式事務請求函數”,是事務控制器對象重要的函數,它負責對“3階段分散式事務”的各個階段進行流程式控制制,其中每一階段,都要和“分散式事務協調服務”進行通信,接受它的指令,完成本地事務資源的控制,比如是提交還是回滾事務資源。下麵我們看看它主要的代碼:

 在上面的函數中,MSF的客戶端服務訪問代理類 Proxy 對象它請求的是“分散式事務協調服務”,即名字為“DTCService”的遠程服務;Proxy的RequestService 方法的最後一個參數,表示服務調用過程中,服務端回調的客戶端函數,在這個回調函數中,提供了3階段分散式事務協議中的各種指令的響應處理,包括:

  • CanCommit--詢問本地事務是否可以提交;
  • PreCommit--預提交指令;
  • Abort--撤銷事務的指令;
  • DoCommit--提交事務的指令。

Proxy對象的RequestService 方法它是一個非同步方法,所以調用它之後代碼會立即向下執行,因此我們用 TaskCompletionSource 對象將非同步方法的結果獲取過程作為一個任務來處理,這樣便可以阻塞非同步方法的執行並等待執行完的結果,如果這個過程中發生了錯誤,就立即回滾事務,即下麵的代碼:

            try
            {
                tcs.Task.Wait();
                return tcs.Task.Result;
            }
            catch (Exception ex)
            {
                PrintLog("MSF DTC({0}) Task Error:{1}", transIdentity,ex.Message);
                TryRollback(dbHelper);
            }

在當前方法 DistTrans3PCRequest 的第二個和第三個參數中,都使用了 AdoHelper類型的參數,它是SOD框架基礎的 數據訪問幫助類,它的“事務計數器” (TransactionCount屬性)有助於正確的開啟事務,化解嵌套的事務,避免用戶的 transFunction 方法內部開啟和提交事務,將事務的最終提交動作交給當前分散式事務控制器。

 

4.2,分散式事務協調服務

 分散式事務控制器在執行本地事務方法的前後,需要有一個分散式事務協調服務來協調它的執行過程,這個協調過程包括以下功能:

  • (提供給控制器)調用指定標識的分散式事務,直到事務執行完成;
  • 管理系統的分散式事務階段,向控制器推送(回調)系統的分散式事務狀態;
  • 分散式事務協調服務需要運行在獨立服務進程中,所以它可以協調多個分散式事務控制器的工作。

下麵是本服務的具體代碼實現,比較簡單:

/// <summary>
    /// 分散式事務協調器服務,基於3PC過程。
    /// </summary>
    public class DTCService:ServiceBase
    {
        private int TransactionResourceCount;
        private DistTrans3PCState CurrentDTCState;

        //private static System.Collections.Concurrent.ConcurrentBag<DistTransInfo> DTResourceList = new System.Collections.Concurrent.ConcurrentBag<DistTransInfo>();

        /// <summary>
        /// 參加指定標識的分散式事務,直到事務執行完成。一個分散式事務包含若幹本地事務
        /// </summary>
        /// <param name="identity">標識一個分散式事務</param>
        /// <returns></returns>
        public bool AttendTransaction(string identity)
        {
            DistTransInfo info = new DistTransInfo();
            info.ClientIdentity = base.CurrentContext.Request.ClientIdentity;
            info.CurrentDTCState = DistTrans3PCState.CanCommit;
            info.LastStateTime = DateTime.Now;
            info.TransIdentity = identity;
            //DTResourceList.Add(info);
            DateTime dtcStart = DateTime.Now;
            //獲取一個當前事務標識的協調器線程
            DTController controller = DTController.CheckStartController(identity);

            CurrentDTCState = DistTrans3PCState.CanCommit;
            while (CurrentDTCState != DistTrans3PCState.Completed)
            {
                //獲取資源伺服器的事務狀態,資源伺服器可能自身或者因為網路情況出錯
                if (!SendDTCState(info, controller, identity))
                    break;
            }
            SendDTCState(info, controller, identity);
            DTController.RemoveController(identity);
            Console.WriteLine("DTC Current Use time:{0}(s)",DateTime.Now.Subtract(dtcStart).TotalSeconds);
            return true;
        }

        private bool SendDTCState(DistTransInfo info, DTController controller, string identity)
        {
            string clientIdentity = string.Format("[{0}:{1}-{2}]", base.CurrentContext.Request.ClientIP, 
                base.CurrentContext.Request.ClientPort, 
                base.CurrentContext.Request.ClientIdentity);
            try
            {
                Console.WriteLine("DTC Service Callback {0} Message:{1}", clientIdentity, CurrentDTCState);
                info.CurrentDTCState = base.CurrentContext.CallBackFunction<DistTrans3PCState, DistTrans3PCState>(CurrentDTCState);
                info.LastStateTime = DateTime.Now;
                CurrentDTCState = controller.GetDTCState(info.CurrentDTCState);
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("DTC Service Callback {0}  Error:{1}", clientIdentity, ex.Message);
                return false;
            }
        }
        


        public override bool ProcessRequest(IServiceContext context)
        {
            return base.ProcessRequest(context);
        }
    }

 在本服務中,通過 base.CurrentContext.CallBackFunction 方法回調分散式控制器,將當前階段系統的分散式狀態告訴控制器。

5,創建訂單相關服務

5.1,訂單服務

訂單服務方法首先它要實例化一個分散式事務控制器對象,在控制器對象裡面完成創建訂單的事務操作,它會首先調用商品服務去更新相應的商品庫存數並取得相關的商品信息,然後接著構造訂單和訂單明細,具體代碼如下:

 /// <summary>
        /// 生成訂單的服務方法
        /// </summary>
        /// <param name="orderId">訂單號</param>
        /// <param name="userId">用戶號</param>
        /// <param name="buyItems">購買的商品簡要清單</param>
        /// <returns>訂單是否創建成功</returns>
        public bool CreateOrder(int orderId,int userId,IEnumerable<BuyProductDto> buyItems)
        {
            //在分散式事務的發起端,需要先定義分散式事務標識:
            string DT_Identity = System.Guid.NewGuid().ToString();
            productProxy.RegisterData = DT_Identity;

            //使用3階段提交的分散式事務,保存訂單到資料庫
            OrderDbContext context = new OrderDbContext();

            DTController controller = new DTController(DT_Identity);
            return controller.DistTrans3PCRequest<bool>(DTS_Proxy, 
                context.CurrentDataBase,
                db =>
                {
                    //先請求商品服務,扣減庫存,並獲取商品的倉庫信息
                    ServiceRequest request = new ServiceRequest();
                    request.ServiceName = "ProductService";
                    request.MethodName = "UpdateProductOnhand";
                    request.Parameters = new object[] { DT_Identity, buyItems };
                    List<SellProductDto> sellProducts = productProxy.RequestServiceAsync<List<SellProductDto>>(request).Result;

                    #region 構造訂單明細和訂單對象
                    //
                    productProxy.Connect();
                    List<OrderItemEntity> orderItems = new List<OrderItemEntity>();
                    OrderEntity order = new OrderEntity()
                    {
                        ID = orderId,
                        OwnerID = userId,
                        OrderTime = DateTime.Now,
                        OrderName = "Prudoct:"
                    };
                    foreach (BuyProductDto item in buyItems)
                    {
                        //註意:在商品資料庫上,前面更新商品,但還沒有提交事務,下麵這個查詢直接使用的話會導致查詢等待,因為SQLSERVER的事務隔離級別是這樣的
                        //所以 GetProductInfo 的實現需要註意。
                        //ProductDto product = this.GetProductInfo(item.ProductId).Result;
                        ProductDto product = this.GetProductInfoSync(item.ProductId);

                        OrderItemEntity temp = new OrderItemEntity()
                        {
                            OrderID = orderId,
                            ProductID = product.ID,
                            BuyNumber = item.BuyNumber,
                            OnePrice = product.Price,
                            ProductName = product.ProductName
                        };
                        temp.StoreHouse = (from i in sellProducts where i.ProductId == temp.ProductID select i.StoreHouse).FirstOrDefault();

                        orderItems.Add(temp);
                        order.OrderName += "," + temp.ProductName;
                        order.AmountPrice += temp.OnePrice * temp.BuyNumber;
                    }
                    //
                    //關閉商品服務訂閱者連接
                    productProxy.Close();

                    #endregion

                    //保存訂單數據到資料庫
                    context.Add<OrderEntity>(order);
                    context.AddList<OrderItemEntity>(orderItems);
                    return true;
                });
        }

註意在上面的方法中,我們創建訂單的代碼並沒有直接提交或者回滾事務,而是通過控制器的 DistTrans3PCRequest 方法傳入了一個AdoHelper對象,由控制器來決定提交或者回滾事務。 其它相關代碼請看Github上的源碼。

5.2,商品服務

商品服務比較簡單,這裡只列出訂單服務需要直接調用的 UpdateProductOnhand方法,具體代碼如下:

public class ProductService:ServiceBase
{
  //其它代碼略

        /// <summary>
        /// 更新商品庫存,並返回商品售賣簡要信息
        /// </summary>
        /// <param name="transIdentity">分散式事務標識</param>
        /// <param name="buyItems">購買的商品精簡信息</param>
        /// <returns></returns>
        public List<SellProductDto> UpdateProductOnhand(string transIdentity, IEnumerable<BuyProductDto> buyItems)
        {
            ProductDbContext context = new ProductDbContext();
            DTController controller = new DTController(transIdentity);
            return controller.DistTrans3PCRequest<List<SellProductDto>>(DTS_Proxy,
                context.CurrentDataBase,
                c =>
                {
                    return InnerUpdateProductOnhand(context,buyItems);
                });
           
        }
}

可以看到,商品服務的更新商品庫存數的方法內部也實例化了一個分散式事務控制器對象,然後在它裡面執行具體的本地事務操作。其它具體代碼略。

需要註意的是,訂單服務在事務執行過程中,多次調用了商品服務的其它方法,這些方法會操作資料庫,如果這些商品服務操作的表正好是更新商品庫存的方法使用的表,此時如果兩個方法操作的資料庫連接不是同一個事務的連接,那麼會導致死鎖。所以商品服務需要設置會話狀態來正確存儲和訪問連接對象,如下代碼:

public class ProductService:ServiceBase
{
  //其它代碼略

        private List<SellProductDto> InnerUpdateProductOnhand(ProductDbContext context, IEnumerable<BuyProductDto> buyItems)
        {
            List<SellProductDto> result = new List<SellProductDto>();

            foreach (BuyProductDto item in buyItems)
            {
                ProductEntity entity = new ProductEntity()
                {
                    ID = item.ProductId,
                    Onhand= item.BuyNumber
                };
                OQL q = OQL.From(entity)
                    .UpdateSelf('-', entity.Onhand)
                    .Where(cmp => cmp.EqualValue(entity.ID) & cmp.Comparer(entity.Onhand, ">=", item.BuyNumber))
                    .END;


                int count = context.ProductQuery.ExecuteOql(q);
                SellProductDto sell = new SellProductDto();
                sell.BuyNumber = item.BuyNumber;
                sell.ProductId = item.ProductId;
                //修改庫存成功,才能得到發貨地
                if (count > 0)
                    sell.StoreHouse = this.GetStoreHouse(item.ProductId);
                result.Add(sell);
            }
            base.CurrentContext.Session.Set<ProductDbContext>("DbContext", context);
            Console.WriteLine("----------1,-Session ID:{0}----------", base.CurrentContext.Session.SessionID);
            return result;
        }

        public override bool ProcessRequest(IServiceContext context)
        {
            context.SessionRequired = true;
            //客戶端(訂單服務)將使用事務標識作為連接的 RegisterData,因此採用這種會話模式
            context.SessionModel = SessionModel.RegisterData;
            return base.ProcessRequest(context);
        }
}

 

5.3,客戶端下單

前面我們討論了分散式事務控制器,分散式事務協調服務,訂單服務和商品服務的具體實現,現在,我們終於可以看看客戶端如何調用訂單服務來創建一個訂單了,請看代碼:

 private static void TestCreateOrder(Proxy client)
        {
            List<BuyProductDto> buyProducts = new List<BuyProductDto>();
            buyProducts.Add(new BuyProductDto() {  ProductId=1, BuyNumber=3});
            buyProducts.Add(new BuyProductDto() { ProductId =2, BuyNumber = 1 });

            int orderId = 2000;
            int userId = 100;

            ServiceRequest request = new ServiceRequest();
            request.ServiceName = "OrderService";
            request.MethodName = "CreateOrder";
            request.Parameters = new object[] { orderId,userId, buyProducts };

            bool result=client.RequestServiceAsync<bool>(request).Result;
            if(result)
                Console.WriteLine("創建訂單成功,訂單號:{0}",orderId);
            else
                Console.WriteLine("創建訂單失敗,訂單號:{0}", orderId);
        }

上面的方法構造了一個準備購買的商品清單,這就是電商“購物車”的簡化版本,另外為了簡便起見,我們直接設定了一個訂單號和用戶號,用這種方式來調用創建訂單的功能。

由於我們的訂單號固定的,所以我們的測試程式第一次會創建成功訂單,而第二次就會失敗,正好可以用它來觀察系統的執行情況。

6,創建訂單的分散式事務測試

6.1,測試環境簡介:

為了簡化測試環境,所有服務實例都運行在一臺PC機器上,包括數據。測試機器的性能如下:

  • CPU:Inter i7-4790 4.00GHz;
  • 記憶體:16GB,可用記憶體:8.7GB
  • 測試開發環境:VS2017 社區版
  • 資料庫:SqlServer 2008 R2

打開VS開發環境,按F5以調試模式編譯運行,設置多啟動項目:

  • DistTransClient
  • TistTransApp

測試項目 TistTransApp下麵的配置文件 PdfNetEF.MessageServiceHost.exe.config    配置內容如下:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="IOCConfigFile" value=".\IOCConfig.xml" />
    <add key="ServerIP" value="127.0.0.1" />
    <add key="ServerPort" value="12345" />
    <add key="ProductUri" value="net.tcp://127.0.0.1:12306"/>
    <add key="OrderUri" value="net.tcp://127.0.0.1:12308"/>
    <!--MSF_DTS_Uri 分散式事務控制器服務連接地址-->
    <add key="MSF_DTS_Uri" value="net.tcp://127.0.0.1:12345"/>
    <!-- 全局緩存配置
    GlobalCacheProvider="CacheServer" 將使用分散式的緩存伺服器,這時候需要配置 CacheConfigFile,其它值將使用本地的緩存
    CacheConfigFile :緩存伺服器的地址的配置文件,也就是本 ServiceHost 運行的另外一些實例
    -->
    <add key="GlobalCacheProvider" value="" />
    <add key="CacheConfigFile" value="CacheServerCfg.xml" />
    <!-- 全局緩存配置結束 -->
    <!--PDF.NET SQL 日誌記錄配置(for 4.0)開始
        記錄執行的SQL語句,關閉此功能請將SaveCommandLog 設置為False,或者設置DataLogFile 為空;
        如果DataLogFile 的路徑中包括~符號,表示SQL日誌路徑為當前Web應用程式的根目錄;
        如果DataLogFile 不為空且為有效的路徑,當系統執行SQL出現了錯誤,即使SaveCommandLog 設置為False,會且僅僅記錄出錯的這些SQL語句;
        如果DataLogFile 不為空且為有效的路徑,且SaveCommandLog 設置為True,則會記錄所有的SQL查詢。
        在正式生產環境中,如果不需要調試系統,請將SaveCommandLog 設置為False 。
    -->
    <add key="SaveCommandLog" value="False" />
    <add key="DataLogFile" value=".\SqlLog.txt" />
    <!--LogExecutedTime 需要記錄的時間,如果該值等於0會記錄所有查詢,否則只記錄大於該時間的查詢。單位毫秒。-->
    <add key="LogExecutedTime" value="0" />
    <!--PDF.NET SQL 日誌記錄配置 結束-->
    <add key="ClientSettingsProvider.ServiceUri" value="" />
  </appSettings>
  <connectionStrings>
    <!--SOD for SQL Server ,框架會自動創建需要的庫  -->
    <add name="OrdersDb" connectionString="Data Source=.;Initial Catalog=OrdersDb;Integrated Security=True" providerName="SqlServer"/>
    <add name="ProductsDb" connectionString="Data Source=.;Initial Catalog=ProductsDb;Integrated Security=True" providerName="SqlServer"/>
   
    <!-- SOD for SQL Server LocalDB  
      註意:請將下麵的連接字元串,修改為你VS 裡面打開的資料庫文件的連接字元串 
    <add name="OrdersDb" connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=~\DataBase\OrdersDB_data.mdf;Integrated Security=True;Connect Timeout=30" providerName="SqlServer"/>
    <add name="ProductsDb" connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=~\DataBase\ProductsDB_data.mdf;Integrated Security=True;Connect Timeout=30" providerName="SqlServer"/>
    -->
    <!-- MSSQLLocalDB 連接示例
    <add name="OrdersDb" connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=E:\Git\MSF-DistTransExample\Host\DataBase\OrdersDB_data.mdf;Integrated Security=True;Connect Timeout=30" providerName="SqlServer"/>
    <add name="ProductsDb" connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=E:\Git\MSF-DistTransExample\Host\DataBase\ProductsDB_data.mdf;Integrated Security=True;Connect Timeout=30" providerName="SqlServer"/>
    -->
   <!-- SOD for Access 2007 ,2013,2016
   <add name="OrdersDb" connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=~\DataBase\OrdersDb.accdb;Persist Security Info=False;" providerName="Access"/>
    <add name="ProductsDb" connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=~\DataBase\Products.accdb;Persist Security Info=False;" providerName="Access"/>
    -->
   <!-- SOD for Access 2000,2003
   <add name="OrdersDb" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=~\DataBase\OrdersDb.mdb;Persist Security Info=False;" providerName="Access"/>
    <add name="ProductsDb" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=~\DataBase\Products.mdb;Persist Security Info=False;" providerName="Access"/>
    -->
    <!-- SOD for SQLite 
   <add name="OrdersDb" connectionString="Data Source=DataBase\OrdersDb.db;" providerName="PWMIS.DataProvider.Data.SQLite,PWMIS.SQLiteClient"/>
    <add name="ProductsDb" connectionString="Data Source=DataBase\Products.db;" providerName="PWMIS.DataProvider.Data.SQLite,PWMIS.SQLiteClient"/>
    -->
  </connectionStrings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
  </startup>
  <system.web>
    <membership defaultProvider="ClientAuthenticationMembershipProvider">
      <providers>
        <add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
      </providers>
    </membership>
    <roleManager defaultProvider="ClientRoleProvider" enabled="true">
      <providers>
        <add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
      </providers>
    </roleManager>
  </system.web>
</configuration>
View Code

配置文件中配置了多種資料庫連接方式,根據你的情況具體選擇。當前是SqlServer.

然後,按照下圖輸入相關的信息:

由於我現在的測試環境是SQLSERVER資料庫,所以不需要初始化資料庫。選擇啟動事務協調器,測試程式會幫我們啟動 協調器服務宿主進程,商品服務宿主進程和訂單服務宿主進程。之後,我們在客戶端控制台輸入 12308,這是訂單服務的埠號,接著客戶端就會調用訂單服務準備創建訂單。

6.2,測試結果

下麵是各種情況下的測試結果,分為訂單創建成功和創建失敗兩種情況。註意我們在分析真正的測試數據之前,要先跑一次服務進行預熱,也就是先進行一次測試,取第二次以後的測試結果。

6.2.1,訂單創建成功:

分散式協調服務:

 

[2018-01-31 17:13:45.807]訂閱消息-- From: 127.0.0.1:53276
[2018-01-31 17:13:45.807]正在處理服務請求--From: 127.0.0.1:53276,Identity:WMI2114256838
>>[PMID:1]Service://DTCService/AttendTransaction/System.String=1b975548-afac-4e7a-be6d-5821bce38ce7
DTC Service Callback [127.0.0.1:53276-WMI2114256838] Message:CanCommit
[2018-01-31 17:13:45.853]訂閱消息-- From: 127.0.0.1:53278
[2018-01-31 17:13:45.854]正在處理服務請求--From: 127.0.0.1:53278,Identity:WMI2114256838
>>[PMID:1]Service://DTCService/AttendTransaction/System.String=1b975548-afac-4e7a-be6d-5821bce38ce7
DTC Service Callback [127.0.0.1:53278-WMI2114256838] Message:CanCommit
DTC Service Callback [127.0.0.1:53276-WMI2114256838] Message:PreCommit
DTC Service Callback [127.0.0.1:53278-WMI2114256838] Message:PreCommit
DTC Service Callback [127.0.0.1:53278-WMI2114256838] Message:DoCommit
DTC Service Callback [127.0.0.1:53278-WMI2114256838] Message:Completed
DTC Current Use time:0.042516(s)
[2018-01-31 17:13:45.897]請求處理完畢(43.0236ms)--To: 127.0.0.1:53278,Identity:WMI2114256838
>>[PMID:1]消息長度:4位元組 -------
result:True
Reponse Message OK.
DTC Service Callback [127.0.0.1:53276-WMI2114256838] Message:DoCommit
[2018-01-31 17:13:45.898]取消訂閱-- From: 127.0.0.1:53278
DTC Service Callback [127.0.0.1:53276-WMI2114256838] Message:Completed
DTC Current Use time:0.1009371(s)
[2018-01-31 17:13:45.909]請求處理完畢(101.9327ms)--To: 127.0.0.1:53276,Identity:WMI2114256838
>>[PMID:1]消息長度:4位元組 -------
result:True
Reponse Message OK.
[2018-01-31 17:13:45.912]取消訂閱-- From: 127.0.0.1:53276

 

訂單服務:

[2018-01-31 17:13:45.798]訂閱消息-- From: 127.0.0.1:53275
[2018-01-31 17:13:45.801]正在處理服務請求--From: 127.0.0.1:53275,Identity:WMI2114256838
>>[PMID:1]Service://OrderService/CreateOrder/System.Int32=2000&System.Int32=100&System.Collections.Generic.List`1[[DistTransDto.BuyProductDto, DistTransDto, Version%Eqv;1.0.0.0, Culture%Eqv;neutral, PublicKeyToken%Eqv;null]]=[{"ProductId":1,"BuyNumber":3},{"ProductI
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.809 receive DTC Controller state:CanCommit
[2018-01-31 17:13:45.879]請求處理完畢(77.9367ms)--To: 127.0.0.1:53275,Identity:WMI2114256838
>>[PMID:1]消息長度:4位元組 -------
result:True
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.879 receive DTC Controller state:PreCommit
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 1PC,Child moniter task has started at time:17:13:45.879
Reponse Message OK.
[2018-01-31 17:13:45.888]取消訂閱-- From: 127.0.0.1:53275
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 2PC,Child moniter task has started at time:17:13:45.888
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 1PC,Child moniter task find DistTrans3PCState has changed,Now is ACK_Yes_2PC,task break!
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.898 receive DTC Controller state:DoCommit
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Try Commit..
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Try Commit..OK
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.903 receive DTC Controller state:Completed
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 3PC Request Completed,use time:0.1019383 seconds.
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 2PC,Child moniter task find DistTrans3PCState has changed,Now is Completed,task break!
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Controller Process Reuslt:True,Receive time:17:13:45.913

 

商品服務:

[2018-01-31 17:13:45.848]正在處理服務請求--From: 127.0.0.1:53277,Identity:WMI2114256838
>>[PMID:1]Service://ProductService/UpdateProductOnhand/System.String=1b975548-afac-4e7a-be6d-5821bce38ce7&System.Collections.Generic.List`1[[DistTransDto.BuyProductDto, DistTransDto, Version%Eqv;1.0.0.0, Culture%Eqv;neutral, PublicKeyToken%Eqv;null]]=[{"ProductId":1
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.855 receive DTC Controller state:CanCommit
----------1,-Session ID:1b975548-afac-4e7a-be6d-5821bce38ce7----------
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 1PC,Child moniter task has started at time:17:13:45.856
[2018-01-31 17:13:45.856]請求處理完畢(8.011ms)--To: 127.0.0.1:53277,Identity:WMI2114256838
>>[PMID:1]消息長度:97位元組 -------
result:[{"StoreHouse":"廣州","ProductId":1,"BuyNumber":3},{"StoreHouse":"廣州","ProductId":2,"BuyNumber":1}]
Reponse Message OK.
[2018-01-31 17:13:45.857]取消訂閱-- From: 127.0.0.1:53277
[2018-01-31 17:13:45.858]訂閱消息-- From: 127.0.0.1:53277
[2018-01-31 17:13:45.867]正在處理服務請求--From: 127.0.0.1:53277,Identity:WMI2114256838
>>[RMID:0]Service://ProductService/GetProductInfo/System.Int32=1
---------2,--Session ID:1b975548-afac-4e7a-be6d-5821bce38ce7----------
[2018-01-31 17:13:45.868]請求處理完畢(1.0005ms)--To: 127.0.0.1:53277,Identity:WMI2114256838
>>[RMID:0]消息長度:53位元組 -------
result:{"ID":1,"Onhand":88,"Price":10.0,"ProductName":"商品0"}
[2018-01-31 17:13:45.869]正在處理服務請求--From: 127.0.0.1:53277,Identity:WMI2114256838
>>[RMID:0]Service://ProductService/GetProductInfo/System.Int32=2
---------2,--Session ID:1b975548-afac-4e7a-be6d-5821bce38ce7----------
[2018-01-31 17:13:45.869]請求處理完畢(0.5005ms)--To: 127.0.0.1:53277,Identity:WMI2114256838
>>[RMID:0]消息長度:53位元組 -------
result:{"ID":2,"Onhand":96,"Price":11.0,"ProductName":"商品1"}
[2018-01-31 17:13:45.870]取消訂閱-- From: 127.0.0.1:53277
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.888 receive DTC Controller state:PreCommit
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 2PC,Child moniter task has started at time:17:13:45.889
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.890 receive DTC Controller state:DoCommit
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Try Commit..
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Try Commit..OK
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Resource at 17:13:45.895 receive DTC Controller state:Completed
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 3PC Request Completed,use time:0.0470229 seconds.
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 1PC,Child moniter task find DistTrans3PCState has changed,Now is Completed,task break!
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) Controller Process Reuslt:True,Receive time:17:13:45.900
MSF DTC(1b975548-afac-4e7a-be6d-5821bce38ce7) 2PC,Child moniter task find DistTrans3PCState has changed,Now is Completed,task break!

 

性能總結:

 訂單創建成功的情況下,分散式協調器服務總共耗時 0.042516(s),訂單服務耗時0.1019383秒,商品服務耗時0.0470229秒。

總體上,執行一個創建訂單的分散式事務,耗時在50毫秒以內。

6.2.2,訂單創建失敗:

分散式協調服務:

[2018-01-31 17:04:11.669]訂閱消息-- From: 127.0.0.1:53201
[2018-01-31 17:04:11.670]正在處理服務請求--From: 127.0.0.1:53201,Identity:WMI2114256838
>>[PMID:1]Service://DTCService/AttendTransaction/System.String=76d175cc-5d40-4d05-adfb-94158b5c2215
DTC Service Callback [127.0.0.1:53201-WMI2114256838] Message:CanCommit
[2018-01-31 17:04:11.679]訂閱消息-- From: 127.0.0.1:53203
[2018-01-31 17:04:11.680]正在處理服務請求--From: 127.0.0.1:53203,Identity:WMI2114256838
>>[PMID:1]Service://DTCService/AttendTransaction/System.String=76d175cc-5d40-4d05-adfb-94158b5c2215
DTC Service Callback [127.0.0.1:53203-WMI2114256838] Message:CanCommit
DTC Service Callback [127.0.0.1:53201-WMI2114256838] Message:Abort
DTC Service Callback [127.0.0.1:53201-WMI2114256838] Message:Completed
DTC Service Callback [127.0.0.1:53203-WMI2114256838] Message:Abort
DTC Current Use time:0.0434914(s)
[2018-01-31 17:04:11.715]請求處理完畢(45.0015ms)--To: 127.0.0.1:53201,Identity:WMI2114256838
>>[PMID:1]消息長度:4位元組 -------
result:True
Reponse Message OK.
DTC Service Callback [127.0.0.1:53203-WMI2114256838] Message:Completed
[2018-01-31 17:04:11.717]取消訂閱-- From: 127.0.0.1:53201
DTC Current Use time:0.0400005(s)
[2018-01-31 17:04:11.724]請求處理完畢(44.4941ms)--To: 127.0.0.1:53203,Identity:WMI2114256838
>>[PMID:1]消息長度:4位元組 -------
result:True
Reponse Message OK.
[2018-01-31 17:04:11.731]取消訂閱-- From: 127.0.0.1:53203

 

訂單服務:

[2018-01-31 17:04:11.662]訂閱消息-- From: 127.0.0.1:53200
[2018-01-31 17:04:11.665]正在處理服務請求--From: 127.0.0.1:53200,Identity:WMI2114256838
>>[PMID:1]Service://OrderService/CreateOrder/System.Int32=2000&System.Int32=100&System.Collections.Generic.List`1[[DistTransDto.BuyProductDto, DistTransDto, Version%Eqv;1.0.0.0, Culture%Eqv;neutral, PublicKeyToken%Eqv;null]]=[{"ProductId":1,"BuyNumber":3},{"ProductI
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Resource at 17:04:11.672 receive DTC Controller state:CanCommit
PDF.NET AdoHelper Query Error:
DataBase ErrorMessage:;違反了 PRIMARY KEY 約束 'PK__Orders__2CE8FBFB7F60ED59'。不能在對象 'dbo.Orders' 中插入重覆鍵。
語句已終止。
SQL:INSERT INTO [Orders]([OerderID],[OrderName],[AmountPrice],[OwnerID],[OrderTime]) VALUES (@P0,@P1,@P2,@P3,@P4)
CommandType:Text
Parameters:
Parameter["@P0"]        =       "2000"                          //DbType=Int32
Parameter["@P1"]        =       "Prudoct:,商品0,商品1"
//DbType=String
Parameter["@P2"]        =       "41"                    //DbType=Single
Parameter["@P3"]        =       "100"                   //DbType=Int32
Parameter["@P4"]        =       "2018-1-31 17:04:11"                    //DbType=DateTime

MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) 1PC,Child moniter task has started at time:17:04:11.710
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Task Error:發生一個或多個錯誤。
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Try Rollback..
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Resource at 17:04:11.711 receive DTC Controller state:Abort
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Try Rollback..OK
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Try Rollback..
[2018-01-31 17:04:11.712]請求處理完畢(46.5004ms)--To: 127.0.0.1:53200,Identity:WMI2114256838
>>[PMID:1]消息長度:5位元組 -------
result:False
Reponse Message OK.
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Try Rollback..OK
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Resource at 17:04:11.714 receive DTC Controller state:Completed
MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) 3PC Request Completed,use time:0.0469998 seconds.
[2018-01-31 17:04:11.716]取消訂

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 申請百度統計後,會得到一段JS代碼,需要插入到每個網頁中去,在Vue.js項目首先想到的可能就是,把統計代碼插入到index.html入口文件中,這樣就全局插入,每個頁面就都有了;這樣做就涉及到一個問題,Vue.js項目是單頁應用,每次用戶瀏覽網站時,訪問內頁時頁面是不會刷新的,也就意味著不會觸發百 ...
  • border屬性 border-width border-style border-color inherit border-style的值:none dotted(點線) dashed(虛線) solid(實線) double(雙線) groove(凹槽) ridge(凸起) inset outs ...
  • 強制類型轉換 將值從一種類型轉換為另一種類型通常稱為類型轉換,這是顯式的情況。隱式的情況被稱為強制類型轉換 在書中,作者還提出一種區分方式: 類型轉換髮生在靜態類型語言的編譯階段,強制類型轉換髮生在動態類型語言的運行時。 JS天生的動態類型語言,所以JS中統一講強制類型轉換。 一、JS中的強制類型轉 ...
  • font連寫屬性 font-style font-variant font-weight font-size/line-height font-family font-size與font-family為必填 font-style 可指定的值為: normal(正常) italic(斜體) obliq ...
  • .toggle-cart-enter-active, .toggle-cart-leave-active { transition: all .3s ease-out; } .toggle-cart-enter, .toggle-cart-leave-active { transform: tran ...
  • wx-jq (一套完全原創的微信小程式插件集合庫) 微信小程式插件,微信小程式組件,微信小程式插件集合,微信小程式組件集合,微信小程式插件學習,微信小程式插件開發, 線上演示: 下載地址: https://gitee.com/dgx/wx-jq 效果展示(截圖大部分來自真機蘋果5s,少量來自模擬器) ...
  • 一個插排引發的設計思想 (一) 觀察者模式 一個插排引發的設計思想 (二) 抽象類與介面 一個插排引發的設計思想 (三) 委托與事件 ...待續.... 前兩篇文章循序漸進的介紹了觀察者模式、抽象類和介面, 並分析了抽象類和介面的不同. 結尾處有這樣的問題: 無論是抽象類還是介面, 都是將設備本身放 ...
  • 一、寫在前面 應用分層這件事情看起來很簡單,但每個程式員都有自己的一套,哪怕是初學者。如何讓一家公司的幾百個應用採用統一的分層結構,並得到大部分程式員的認同呢?這可不是件簡單的事情,接下來以我們真實案例與大家一起探討,先問大家兩個技術問題: 服務的調用代碼你覺得放到哪一層好呢?A表現層;B業務邏輯層 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...