在上一篇 《消息服務框架(MSF)應用實例之分佈式事務三階段提交協議的實現》中,咱們分析了分佈式事務的三階段提交協議的原理,如今咱們來看看如何使用消息服務框架(MSF)來具體實現而且看用它來實現的一些優點。html
首先,從Github克隆項目源碼,地址:https://github.com/bluedoctor/MSF-DistTransExamplegit
解決方案以下圖:github
咱們看到解決方案有4個項目:web
下面先介紹本示例要解決的業務,並經過這個業務來分析分佈式事務的執行過程。數據庫
在本示例中,使用的是電商系統最多見的業務場景:下單業務,它的業務流程也歸納起來比較簡單:緩存
建立訂單:服務器
固然,在具體的電商業務系統中,下單業務比較複雜,特別是對庫存的扣減方式,但大致的業務流程就是這樣的,咱們今天的重點是研究這個下單過程在分佈式環境下如何實現。網絡
假設咱們的電商平臺使用微服務架構的,包含了用戶服務,商品服務,訂單服務和支付服務,這4個服務在下單業務中的功能分別以下:架構
下面是這4個服務在建立訂單的業務流程圖:併發
上圖中,支付服務是第三方提供的服務,須要用戶在建立訂單後跳轉調用,因此本質上不是訂單服務直接調用,訂單服務須要提供一個支付完成的回調通知接口,完成有效訂單的確認。 而用戶服務做爲服務調用的發起方,它會傳遞必要的信息給訂單服務,所以,對於「建立訂單」這個具體的業務功能,它涉及的須要同時進行操做的只有建立訂單和扣減庫存這兩個子業務,而且要求這2個子業務操做具備原子性,即要麼同時成功,要麼同時失敗撤銷,因此這兩個操做組成一個事務操做,在咱們當前的場景中,它是一個分佈式事務。
在本例中,咱們使用消息服務框架(MSF)來實現分佈式事務,爲了更加真實的模擬微服務架構,咱們將建立訂單相關的服務劃分爲3個獨立的進程,這些進程就是MSF.Host服務容器,這裏分爲3個服務容器:
下面是這3個服務容器的進程調用關係圖:
下面來看建立訂單的分佈式事務處理過程,爲簡單起見,只討論正常的流程,其中異常的流程,請參考原文對於3階段提供分佈式事務的具體原理。
1,客戶端調用訂單服務的建立訂單方法;(上圖步驟1)
2,訂單服務實例化,接受一個訂單號,用戶號,要購買的商品清單3個參數來建立訂單;(上圖步驟1)
3,建立訂單的方法向分佈式事務控制器進行本地事務註冊,傳入建立訂單的事務方法(委託);(上圖步驟2)
4,建立訂單的事務方法遠程調用商品服務,更新商品庫存;(上圖步驟3)
5,商品服務的更新商品庫存方法向分佈式事務控制器進行本地事務註冊,傳入具體更新庫存的事務方法(委託);(上圖步驟4)
6,商品服務執行完成更新庫存的方法,向訂單服務返回必要的信息,準備好提交事務;(上圖步驟5)
7,訂單服務收到商品服務的返回信息,構建好訂單和訂單明細,準備好提交事務;(上圖步驟6)
8,分佈式事務控制器檢測到註冊的各事務資源服務器(商品服務和訂單服務)都已經準備好提交事務,向它們發出提交指令;
9,商品服務和訂單服務收到提交指令,提交本地事務,事務資源服務方法執行完成;(上圖步驟7,8)
10,分佈式事務控制器收到事務資源服務器的反饋,登記本次分佈式事務執行完成;
11,訂單服務標記建立訂單成功,向客戶端返回信息。
分佈式事務控制器是提供給事務資源服務使用的組件,在本示例中是類 DTController,它提供了以下重要方法:
其中「3階段分佈式事務請求函數」,是事務控制器對象重要的函數,它負責對「3階段分佈式事務」的各個階段進行流程控制,其中每一階段,都要和「分佈式事務協調服務」進行通訊,接受它的指令,完成本地事務資源的控制,好比是提交仍是回滾事務資源。下面咱們看看它主要的代碼:
在上面的函數中,MSF的客戶端服務訪問代理類 Proxy 對象它請求的是「分佈式事務協調服務」,即名字爲「DTCService」的遠程服務;Proxy的RequestService 方法的最後一個參數,表示服務調用過程當中,服務端回調的客戶端函數,在這個回調函數中,提供了3階段分佈式事務協議中的各類指令的響應處理,包括:
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 方法內部開啓和提交事務,將事務的最終提交動做交給當前分佈式事務控制器。
分佈式事務控制器在執行本地事務方法的先後,須要有一個分佈式事務協調服務來協調它的執行過程,這個協調過程包括如下功能:
下面是本服務的具體代碼實現,比較簡單:
/// <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 方法回調分佈式控制器,將當前階段系統的分佈式狀態告訴控制器。
訂單服務方法首先它要實例化一個分佈式事務控制器對象,在控制器對象裏面完成建立訂單的事務操做,它會首先調用商品服務去更新相應的商品庫存數並取得相關的商品信息,而後接着構造訂單和訂單明細,具體代碼以下:
/// <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上的源碼。
商品服務比較簡單,這裏只列出訂單服務須要直接調用的 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); } }
前面咱們討論了分佈式事務控制器,分佈式事務協調服務,訂單服務和商品服務的具體實現,如今,咱們終於能夠看看客戶端如何調用訂單服務來建立一個訂單了,請看代碼:
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); }
上面的方法構造了一個準備購買的商品清單,這就是電商「購物車」的簡化版本,另外爲了簡便起見,咱們直接設定了一個訂單號和用戶號,用這種方式來調用建立訂單的功能。
因爲咱們的訂單號固定的,因此咱們的測試程序第一次會建立成功訂單,而第二次就會失敗,正好能夠用它來觀察系統的執行狀況。
爲了簡化測試環境,全部服務實例都運行在一臺PC機器上,包括數據。測試機器的性能以下:
打開VS開發環境,按F5以調試模式編譯運行,設置多啓動項目:
測試項目 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>
配置文件中配置了多種數據庫鏈接方式,根據你的狀況具體選擇。當前是SqlServer.
而後,按照下圖輸入相關的信息:
因爲我如今的測試環境是SQLSERVER數據庫,因此不須要初始化數據庫。選擇啓動事務協調器,測試程序會幫咱們啓動 協調器服務宿主進程,商品服務宿主進程和訂單服務宿主進程。以後,咱們在客戶端控制檯輸入 12308,這是訂單服務的端口號,接着客戶端就會調用訂單服務準備建立訂單。
下面是各類狀況下的測試結果,分爲訂單建立成功和建立失敗兩種狀況。注意咱們在分析真正的測試數據以前,要先跑一次服務進行預熱,也就是先進行一次測試,取第二次之後的測試結果。
分佈式協調服務:
[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毫秒之內。
分佈式協調服務:
[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]取消訂閱-- From: 127.0.0.1:53200 MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Controller Process Reuslt:True,Receive time:17:04:11.719 MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) 1PC,Child moniter task find DistTrans3PCState has changed,Now is Completed,task break!
商品服務:
[2018-01-31 17:04:11.674]訂閱消息-- From: 127.0.0.1:53202 [2018-01-31 17:04:11.675]正在處理服務請求--From: 127.0.0.1:53202,Identity:WMI2114256838 >>[PMID:1]Service://ProductService/UpdateProductOnhand/System.String=76d175cc-5d40-4d05-adfb-94158b5c2215&System.Collections.Generic.List`1[[DistTransDto.BuyProductDto, DistTransDto, Version%Eqv;1.0.0.0, Culture%Eqv;neutral, PublicKeyToken%Eqv;null]]=[{"ProductId":1 MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Resource at 17:04:11.681 receive DTC Controller state:CanCommit ----------1,-Session ID:76d175cc-5d40-4d05-adfb-94158b5c2215---------- MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) 1PC,Child moniter task has started at time:17:04:11.682 [2018-01-31 17:04:11.682]請求處理完畢(7.5003ms)--To: 127.0.0.1:53202,Identity:WMI2114256838 >>[PMID:1]消息長度:97字節 ------- result:[{"StoreHouse":"廣州","ProductId":1,"BuyNumber":3},{"StoreHouse":"廣州","ProductId":2,"BuyNumber":1}] Reponse Message OK. [2018-01-31 17:04:11.685]取消訂閱-- From: 127.0.0.1:53202 [2018-01-31 17:04:11.686]訂閱消息-- From: 127.0.0.1:53202 [2018-01-31 17:04:11.687]正在處理服務請求--From: 127.0.0.1:53202,Identity:WMI2114256838 >>[RMID:0]Service://ProductService/GetProductInfo/System.Int32=1 ---------2,--Session ID:76d175cc-5d40-4d05-adfb-94158b5c2215---------- [2018-01-31 17:04:11.688]請求處理完畢(1.5019ms)--To: 127.0.0.1:53202,Identity:WMI2114256838 >>[RMID:0]消息長度:53字節 ------- result:{"ID":1,"Onhand":88,"Price":10.0,"ProductName":"商品0"} [2018-01-31 17:04:11.690]正在處理服務請求--From: 127.0.0.1:53202,Identity:WMI2114256838 >>[RMID:0]Service://ProductService/GetProductInfo/System.Int32=2 ---------2,--Session ID:76d175cc-5d40-4d05-adfb-94158b5c2215---------- [2018-01-31 17:04:11.694]請求處理完畢(4ms)--To: 127.0.0.1:53202,Identity:WMI2114256838 >>[RMID:0]消息長度:53字節 ------- result:{"ID":2,"Onhand":96,"Price":11.0,"ProductName":"商品1"} [2018-01-31 17:04:11.694]取消訂閱-- From: 127.0.0.1:53202 MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Resource at 17:04:11.714 receive DTC Controller state:Abort MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Try Rollback.. MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Try Rollback..OK MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Resource at 17:04:11.717 receive DTC Controller state:Completed MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) 3PC Request Completed,use time:0.0410005 seconds. MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) 1PC,Child moniter task find DistTrans3PCState has changed,Now is Completed,task break! MSF DTC(76d175cc-5d40-4d05-adfb-94158b5c2215) Controller Process Reuslt:True,Receive time:17:04:11.731
性能總結:
訂單建立成功的狀況下,分佈式協調器服務總共耗時 0.0434914(s),訂單服務耗時0.0469998秒,商品服務耗時0.0410005秒。
整體上,執行一個建立訂單的分佈式事務,耗時在50毫秒之內。
從上面的測試結果看到,不管是訂單建立成功提交事務,仍是訂單建立失敗回滾事務,整體上事務執行時間都在50毫秒之內,屢次測試也沒用發現某個事務節點嚴重等待耗時的狀況。
上面測試單個分佈式事務執行在50毫秒之內,那麼併發執行性能怎麼樣呢?
能夠將客戶端的代碼稍加改造,以下:
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 = 7000; 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); Console.WriteLine("------開始併發下單測試,按任意鍵繼續---------"); Console.ReadLine(); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); int taskCount = 2; List<Task> tasks = new List<Task>(); for (int i = 1; i <= taskCount; i++) { Proxy client1 = new Proxy(); client1.ServiceBaseUri = client.ServiceBaseUri; ServiceRequest request1 = new ServiceRequest(); request1.ServiceName = "OrderService"; request1.MethodName = "CreateOrder"; request1.Parameters = new object[] { orderId+i, userId, buyProducts }; var task = client1.RequestServiceAsync<bool>(request1); tasks.Add(task); Console.WriteLine("添加第 {0}個任務.",i); } Console.WriteLine("{0} 個訂單請求任務建立完成,開始等待全部任務執行完成!",taskCount); Task.WaitAll(tasks.ToArray()); Console.WriteLine("全部任務執行完成!"); sw.Stop(); Console.WriteLine("總耗時:{0}(s),TPS:{1}",sw.Elapsed.TotalSeconds,(double)taskCount /sw.Elapsed.TotalSeconds); }
上面程序中,變量 taskCount 表示要併發下單的任務數,TPS表示每秒處理的事務數,是一個經常使用的性能指標單位。
先以2個併發下單任務數測試,結果以下:
------開始併發下單測試,按任意鍵繼續--------- 添加第 1個任務. 添加第 2個任務. 2 個訂單請求任務建立完成,開始等待全部任務執行完成! 全部任務執行完成! 總耗時:0.0503977(s),TPS:39.6843506747332
TPS接近40個,還能夠;
再以3個併發任務數測試,結果以下:
------開始併發下單測試,按任意鍵繼續--------- 添加第 1個任務. 添加第 2個任務. 添加第 3個任務. 3 個訂單請求任務建立完成,開始等待全部任務執行完成! 全部任務執行完成! 總耗時:0.3463996(s),TPS:8.66051808373913
3個併發後,性能降低很快,只有8個多TPS了。
直接測試10個併發,結果以下:
------開始併發下單測試,按任意鍵繼續--------- 添加第 1個任務. 添加第 2個任務. 添加第 3個任務. 添加第 4個任務. 添加第 5個任務. 添加第 6個任務. 添加第 7個任務. 添加第 8個任務. 添加第 9個任務. 添加第 10個任務. 10 個訂單請求任務建立完成,開始等待全部任務執行完成! 全部任務執行完成! 總耗時:8.7288772(s),TPS:1.14562271537054
到10個併發後,TPS降低的很厲害,只有1個多了。
一直測試到50個併發,TPS也只有1個多,初步結論在10個以上併發TPS只能有1個多,看來在高併發下,分佈式事務的性能的確不理想。
不過,本次測試的電商下單業務邏輯稍微有點複雜,其中構造訂單的過程當中須要反覆查詢幾回商品庫的信息,並且還有插入訂單明細的操做,在數據庫併發訪問的時候很容易引發表鎖,這也是性能降低很明顯的緣由。
若是是銀行跨行轉帳這樣比較簡單的例子,可能性能要高些,你們能夠本身去作個測試。
消息服務框架(MSF)成功的實現了基於3階段提交的分佈式事務協議,而且事務執行性能在分佈式環境下是能夠接受的。
當前實現過程當中,利用消息服務框架的長鏈接特性,它能夠及時的發現網絡異常狀況而不會出現出現「傻等」的問題(等到超時),這能夠保證分佈式事務執行的可靠性和效率。
爲何長鏈接可以改善分佈式事務的效率?
你能夠這樣理解,有A,B,C三個分佈式服務,它們須要完成一致性的操做,常規的作法是用複雜的分佈式事務框架,可是,若是有一個M節點,它將 A,B,C鏈接起來而且不中斷,調用它們的服務是否是像調用本地方法一個道理?這樣,只須要在M節點開啓和提交事務,就等於完成了分佈式事務了。
iMSF的分佈式事務基於本地事務實現的,充分利用了iMSF的長鏈接通訊能力,使得分佈式事務就像是本地事務同樣。
分佈式事務在高併發下性能表現不理想,咱們在實際項目中須要注意這個問題,但這不是iMSF的特例,而是分佈式事務廣泛的問題。所以,要解決高性能問題,不二之選是在系統設計的時候就考慮消息驅動模式,使用Actor併發模型,iMSF框架支持Actor模型。