分布式事务——【新生入学系统】,新生入学



前言  

  了解分布式事务之前,先要明白事务到底是一个什么东西。事务(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>


总结

  分布式不仅仅体现在事务中,它代表的是团队合作的精神,把一个大的问题打散成零碎的小问题分配给更多的计算机各个击破。后来也在网上搜了搜分布式和集群的区别:分布式是以缩短单个任务的执行时间来提升效率的,集群是通过提高单位时间内执行的任务数来提升效率的。分布式就像做一个复杂的算术题,把加减乘除分配给不同的人研究,每个人研究一块,总体就出来了。集群是一群人一起做题,固定数量的题就被均摊了。

  信息社会瞬息万变,云、大数据必将是下一波信息浪潮。

 



相关内容