【ITOO】--分散式事務

ZeroWM發表於2015-03-15


前言  

  瞭解分散式事務之前,先要明白事務到底是一個什麼東西。事務(Transaction)就像搞物件,要麼做男女朋友,要麼就做陌生人,沒有好朋友這麼一說。

  官方解釋:事務提供了一種機制將一個活動涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作只有在所有操作均能正常執行的情況下方能提交,只要其中任一操作執行失敗,都將導致整個事務的回滾。簡單說:事務提供了一種“要麼不做,要麼全做”的機制。瞬間感覺,事務有了東北大老爺們的氣概~


事務特性

  原子性(Atomicity)、一致性(Consistence)、隔離性(Isolation)、永續性(Durability),這幾個特性都是圍繞事務原子性來展開的。原子性體現了事務的不可分割,整個事務內部與外界隔離開,從一個一致性的狀態轉換到另外一個一致性的狀態,最後事務執行完成後,把記憶體中的資料存入到資料庫中,就叫做持久化。


分散式事務

  個人理解是活動涉及多個伺服器的事務稱為分散式事務。當一個分散式事務結束時,事務的原子性要求所有的伺服器要麼提交該事務,要麼全部終止。其中的一個伺服器充當協調者,通過協議讓所有的伺服器保持相同的結果。

  舉個小例子,許可權可以控制學生通過郵箱來登入整個雲平臺。基礎為許可權提供學生資訊,如果基礎修改了學生的學號,那麼相應的在許可權系統、考試系統中就要進行學生學號的同步修改。

  事務不在侷限於在一個系統內部執行,可以跨系統實現資料的同步。


例項

  我所知道的分散式事務一般存在於資料庫裡,或者VS程式碼裡面。下面是簡單的一個小demo,希望大家能夠對WCF中的分散式事務更加的瞭解。

  程式碼背景:客戶端A給服務端B轉賬,A減少多少金額,B增加相應的轉賬資訊,金額。


客戶端:


IAccount類:

<span style="font-family:KaiTi_GB2312;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCF_ATTransTest.Service
{
    //建立服務契約B轉賬WCF服務
    [ServiceContract]<span style="font-family: SimSun;">//特性</span>

    public interface IAccountB
    {
        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Allowed)]//允許客戶端使用事務;
        void deposit(int depositorid, double amount);//存款的方法
    }

}
</span>

  TransactionFlowOption.Allowed表示opreation可以參與事務流。到服務端的IAccountB中可以看到另外一個屬性:TransactionFlowOption. Mandatory。這個屬性是必須根據傳入的事務進行分散式事務,如果客戶端沒有事務便丟擲異常。


Program控制檯:

<span style="font-family:KaiTi_GB2312;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.ServiceModel;
using System.ServiceModel.Channels;
using WCF_ATTransTest.Service;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;


//建立A轉賬客戶端
namespace WCF_ATTransTest.Client
{
    class Program
    {
        static void Main(string[] args)
        {

            /*
             客戶端和服務端的通訊是如何發生的呢?
             * 根據我們前面的學習,從實際操作上看,我們在服務端定義好協定和實現,
             * 配置好公開的終結點,開啟後設資料交換,在客戶端新增服務引用,然後就
             * 直接new出來一個叫做XXXClient 的物件,這個物件擁有服務協定裡的所有方法,直接呼叫就可以了。
             * 實際上客戶端和服務端中間要建立一個通道,通過通道交流。
             * 客戶端會在這兩個終結點之間建立一個通道,然後把對服務端服務的
             * 呼叫封裝成訊息沿通道送出,伺服器端獲得訊息後在伺服器端建立服務物件
             * ,然後執行操作,將返回值再封裝成訊息發給客戶端。
             */
            //System.ServiceModel提供了一個名為ChannelFactory<>的類,他接受服務協定介面作為泛型引數
            //,這樣new出來的例項叫做服務協定XXX的通道工廠。

            //New一個通道工廠的例項 ,服務協定介面作為泛型引數。
            ChannelFactory<IAccountB> myFactory = new ChannelFactory<IAccountB>("endpointConfig");
            //產生一個通道,工廠是協定介面建立的,所以在這也實先了IAccountB介面
            IAccountB myClient = myFactory.CreateChannel();
            Double amount = 500;
            int depositorid = 1;
            using (TransactionScope scop = new TransactionScope())//使程式碼塊成為事務性程式碼
            {
                #region 資料訪問,在指定賬戶上減少存款
                string connstr = ConfigurationManager.ConnectionStrings["ConnStr"].ToString();
                //A客戶端減少 金額呼叫B的WCF服務
                SqlCommand mySqlCommand = new SqlCommand("update account set amount = amount - @amount where depositorid = @depositorid ");
                mySqlCommand.Connection = new SqlConnection(connstr);

                SqlParameter par1 = new SqlParameter("@amount", SqlDbType.Decimal);
                par1.Value = amount;
                mySqlCommand.Parameters.Add(par1);

                par1 = new SqlParameter("@depositorid", SqlDbType.Int);
                par1.Value = depositorid;
                mySqlCommand.Parameters.Add(par1);

                mySqlCommand.Connection.Open();
                mySqlCommand.ExecuteNonQuery();
                mySqlCommand.Connection.Close();
                #endregion

                try
                {
                    myClient.deposit(depositorid, amount);//呼叫通道的協定方法
                    scop.Complete();
                }
                catch (Exception e)
                {
                    Transaction.Current.Rollback();//如果事務執行不成功,回滾到A沒有減錢的狀態<span style="font-family: SimSun;font-size:18px;"></span><pre name="code" class="csharp">
</span><span style="font-family: KaiTi_GB2312;">} } } }}</span>


服務端:

IAccountB類:

<span style="font-family:KaiTi_GB2312;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCF_ATTransTest.Service
{

    //建立系統B轉賬WCF服務
    [ServiceContract]
    public interface IAccountB
    {
        [OperationContract]
       
        [TransactionFlow(TransactionFlowOption.Mandatory)]<span style="font-family: Arial, Helvetica, sans-serif;"> //operation必須跟隨傳入的事務,參與分散式事務</span>
        void deposit(int depositorid, double amount);//轉賬:賬戶Id,轉賬金額
    }

}
</span>


AccountBService類:

<span style="font-family:KaiTi_GB2312;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;
using Microsoft.KtmIntegration;
using System.IO;


//WCF轉賬的服務的實現。
namespace WCF_ATTransTest.Service
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class AccountBService : IAccountB
{
    [OperationBehavior(TransactionScopeRequired = true)]//客戶端可以發起分散式事務了
    public void deposit(int depositorid, double amount)
    {
        Transaction ambientTransaction = Transaction.Current;

        #region 新建事務性檔案

        string path = @"c:\test.txt";//實現的deposit操作中完成兩個任務,先轉賬資訊寫入C:\text.text,用到了事務性檔案TransactedFile.Open
        FileStream fs = TransactedFile.Open(path, System.IO.FileMode.Create, 
            System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
        //FileStream fs = File.Open(path, System.IO.FileMode.Create, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
        string fileContent = string.Format("從系統A轉賬到系統B\r\n使用者ID:{0}\r\n轉賬金額為:{1}", depositorid.ToString(), amount.ToString());
        byte[] byteArrar = Encoding.UTF8.GetBytes(fileContent);
        fs.Write(byteArrar, 0, byteArrar.Count());
        fs.Flush();
        fs.Close();
        #endregion

        #region 資料訪問,在指定賬戶上增加存款
        string connstr = ConfigurationManager.ConnectionStrings["ConnStr"].ToString();
        SqlCommand mySqlCommand = new SqlCommand("update account set amount = amount + @amount where depositorid = @depositorid ");
        mySqlCommand.Connection = new SqlConnection(connstr);

        SqlParameter par1 = new SqlParameter("@amount", SqlDbType.Decimal);
        par1.Value = amount;
        mySqlCommand.Parameters.Add(par1);

        par1 = new SqlParameter("@depositorid", SqlDbType.Int);
        par1.Value = depositorid;
        mySqlCommand.Parameters.Add(par1);

        mySqlCommand.Connection.Open();
        mySqlCommand.ExecuteNonQuery();
        mySqlCommand.Connection.Close();
        #endregion
     }
}
}
</span>


總結

  分散式不僅僅體現在事務中,它代表的是團隊合作的精神,把一個大的問題打散成零碎的小問題分配給更多的計算機各個擊破。後來也在網上搜了搜分散式和叢集的區別:分散式是以縮短單個任務的執行時間來提升效率的,叢集是通過提高單位時間內執行的任務數來提升效率的。分散式就像做一個複雜的算術題,把加減乘除分配給不同的人研究,每個人研究一塊,總體就出來了。叢集是一群人一起做題,固定數量的題就被均攤了。

  資訊社會瞬息萬變,雲、大資料必將是下一波資訊浪潮。

 



相關文章