如何让普通变量也支持事务回滚?

有一次和人谈起关于事务的话题,谈到怎样的资源才能事务型资源。除了我们经常使用的数据库、消息队列、事务型文件系统(TxF)以及事务性注册表(TxR)等,还有那些资源直接可以纳入事务进行状态的管理呢?我说如果我们按照.NET事务模型的规范对相应的资源进行合理的封装,原则上我们可以让任何可编程的资源成为事务型资源。本篇文章中,我将通过简单的编程将一个普通的变量变成支持事务,让变量的值也可以回滚,以确保事务前后的数据一致性。

一、什么是事务型的变量

本文中所说的事务型变量指的是这样的变量:

  • 在事务开始前,变量的初始值会被保存;
  • 在事务中对变量的赋值只有在事务被成功提交后才会真正赋值给变量;
  • 如果事务中止导致回滚,变量的值将会恢复到事务开始之前的状态。

上面的对事务型变量的描述可以通过下面的程序来体现:变量v在初始化时被赋值为1。然后通过TransactionScope开始一个事务,并将变量纳入该事务之中。在事务范围内将值赋值为2,然后调用DoSomething方法,并提交事务。如果DoSomething执行过程中抛出异常,整个事务将会回滚。当整个事务中止回滚后,变量v的值回复到事务开始之前的状态,即值为1。

   1: static void Main(string[] args)
   2: {
   3:     TransactionalVariable<int> v = new TransactionalVariable<int>(1);
   4:     try
   5:     {
   6:         using (TransactionScope transactionScope = new TransactionScope())
   7:         {
   8:             Transaction.Current.EnlistPromotableSinglePhase(v);
   9:             v.Value = 2;
  10:             DoSomething();
  11:             transactionScope.Complete();
  12:         }
  13:     }
  14:     catch
  15:     { }
  16:     Debug.Assert(v.Value == 1);
  17: }

二、简单谈谈System.Transactions事务模型

事务型变量的性质已经说得很清楚了,现在根本的任务就是如何来定义这样的一个事务性变量类型,即上面实例程序中的TransactionalVariable<T>类型。不过在这之前,我们有必要简单看谈谈System.Transactions的事务模型。对于所有的事务参与者,按照各自在整个事务生命周期各个阶段所承担的职能,大致扮演着如下三种角色:

  • 应用(Application)、服务(Service)或者组件(Component):代表用户程序,或者是承载着某功能的服务(Service)或者组件(Component);
  • 资源管理器(RM:Resource Manager):代表用于管理具体事务型资源的软件程序,比如数据库或者队列(MSMQ)等;
  • 事务管理器(TM: Transaction Manager):代表管理整个事务的中间件程序,为应用和资源管理器提供基本的事务控制服务。

关于System.Transactions具体的事务管理模型,可以参考我的文章《谈谈分布式事务之二:基于DTC的分布式事务管理模型[上篇]》,在这里就不在赘言介绍了。总而言之,只要我们能够为变量编写相应的“资源管理器”,我们就能够将其纳入到System.Transactions.Transaction之中。在System.Transactions体系中,编写事务管理器是一件很简单的事情,一种非常直接的方式就是实现IPromotableSinglePhaseNotification这么一个接口。实例代码中使用的TransactionalVariable<T>类型就是这么定义的。

三、通过实现IPromotableSinglePhaseNotification接口定义TransactionalVariable<T>

在具体介绍TransactionalVariable<T>的定义之前,我们不妨来看看IPromotableSinglePhaseNotification接口是如何定义的。下面的代码片断反映了IPromotableSinglePhaseNotification的定义:加上从父接口继承下来的成员,整个IPromotableSinglePhaseNotification接口一共具有4个方法成员。Initialize方法会在资源纳入事务的时候被调用,用于执行一些初始化操作。SinglePhaseCommit、Rollback和Promote用于通知事务正在被提交、回滚和提升。

   1: public interface IPromotableSinglePhaseNotification : ITransactionPromoter
   2: {    
   3:     void Initialize();
   4:     void Rollback(SinglePhaseEnlistment singlePhaseEnlistment);
   5:     void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment);
   6: }
   7: public interface ITransactionPromoter
   8: {   
   9:     byte[] Promote();
  10: }

TransactionalVariable<T>直接实现了IPromotableSinglePhaseNotification接口,下面是全部的定义。TransactionalVariable<T>中定义了两个数据成员,字段_originalValue和属性Value代表变量的初始值和当前值。

  • Initialize:将当前值赋给初始值,此时两者具有相同的值;
  • Rollback:将初始值赋给当前值,并调用SinglePhaseEnlistment的Aborted方法通知终止事务,这意味着事务过程中对变量的修改都将丢失;
  • SinglePhaseCommit:将当前值赋给初始值,并调用SinglePhaseEnlistment的Committed方法通知提交事务,相当于将事务中对变量的修改正式生效;
  • Promote:由于我们只打算让我们的事务型变量支持本地事务的场景,并不对分布式事务提供支持,在这里直接抛出一个异常
   1: using System.Transactions;
   2: namespace Artech.TransactionalObjects
   3: {
   4:     public class TransactionalVariable<T> : IPromotableSinglePhaseNotification
   5:     {
   6:         private T _originalValue;
   7:         public T Value { get; set; }
   8:         
   9:  
  10:         public TransactionalVariable(T variable)
  11:         {
  12:             this.Value = variable;
  13:         }
  14:  
  15:         public void Initialize()
  16:         {
  17:             _originalValue = this.Value;
  18:         }
  19:  
  20:         public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
  21:         {
  22:             this.Value = _originalValue;
  23:             singlePhaseEnlistment.Aborted();
  24:         }
  25:  
  26:         public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
  27:         {
  28:             _originalValue = this.Value;
  29:             singlePhaseEnlistment.Committed();
  30:         }
  31:  
  32:         public byte[] Promote()
  33:         {
  34:             throw new TransactionException("TransactionalVariable just only support local transaction.");
  35:         }
  36:     }
  37: }

以上就是所有的实现,并没有什么特别之处,仅仅就是通过实现对初始值的缓存,进而实现在事务中止时能够将值恢复到之前的状态。你可以通过这里下载该例子。不过,这个例子仅仅是一个简单的模拟演示而已,还有很多不足之处。比如事务四大属性的隔离性在TransactionalVariable<T>就不能体现出来。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA高级架构

Java阿里面试题

2991
来自专栏君赏技术博客

建议大型项目用上Try Catch建议大型项目用上Try Catch

我们在平时项目做功能的时候,经常会遇到崩溃的情况。如果是我们在开发测试阶段,我们可以找到原因修复。但是遇到已经上线,出现这种问题。要么使用JSPatch进行热修...

781
来自专栏运维一切

ceph对象存储折腾记 原

###前言 一直想弄对象存储,以前弄过一次,不是很理解region是个什么东西,后来时间和工作上的原因没有再折腾,这两天闲了下来,再次折腾了一次。我是参考的ce...

1541
来自专栏与神兽党一起成长

解析XML和JSON内容的一点技巧

在没有统一标准的情况下,一个系统对接多个外部系统往往会遇到请求接口响应数据异构的情况,有可能返回的是XML,也有可能返回 JSON。除了返回类型不同,内容结构也...

1732
来自专栏积累沉淀

Java批处理

批处理 JDBC对批处理的操作,首先简单说一下JDBC操作sql语句的简单机制。 JDBC执行数据库操作语句,首先需要将sql语句打包成为网络字...

4205
来自专栏FreeBuf

缓冲区溢出攻击初学者手册(更新版)

说明 之前版本翻译质量不佳,本人赵阳在这里对本文的读者表示深深的歉意。由于本人的疏忽和大意导致您不能很好的读完这篇文章,同时也对原文内容进行了破坏,也对IDF和...

2749
来自专栏大闲人柴毛毛

轻量级线程池的实现

写在前面 最近因为项目需要,自己写了个单生产者-多消费者的消息队列模型。多线程真的不是等闲之辈能玩儿的,我花了两个小时进行设计与编码,却花了两天的时间调试与运...

4924
来自专栏玄魂工作室

PYTHON黑帽编程 4.1 SNIFFER(嗅探器)之数据捕获(下)

上一节(《4.1 SNIFFER(嗅探器)之数据捕获(上)》)中, 我们讲解了通过Raw Socket的方式来编写Sniffer的基本方法。 本节我们继续来编写...

5215
来自专栏开发技术

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

    老公酷爱网络游戏,老婆无奈,只得告诫他:你玩就玩了,但是千万不可以在游戏里找老婆,不然,哼哼。。。     老公嘴角露出了微笑:放心吧亲爱的,我绝对不会...

2682
来自专栏安恒网络空间安全讲武堂

从零基础到成功解题之0ctf-ezdoor

2104

扫码关注云+社区

领取腾讯云代金券