如何让普通变量也支持事务回滚?
有一次和人谈起关于事务的话题,谈到怎样的资源才能事务型资源。除了我们经常使用的数据库、消息队列、事务型文件系统(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>就不能体现出来。
- insert导致的性能问题大排查(r11笔记第26天)
- NYOJ-----最少乘法次数
- nyOJ-----韩信点兵
- HDUOJ-----A == B ?
- 用Oracle的眼光来学习MySQL 5.7的sys(上)(r11笔记第24天)
- Golang下通过syscall调用win32的api
- NYOJ----蛇形填数
- Golang语言 syscall 例子
- 用Oracle的眼光来学习MySQL 5.7的sys(下)(r11笔记第25天)
- HDUOJ-----Climbing Worm
- 闪回原理测试(二)(r11笔记第23天)
- SQL复习之为数据库用户赋予权限
- linux下syscall函数,SYS_gettid,SYS_tgkill
- 数据库收缩数据文件的尝试(三)(r11笔记第22天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS7-samba文件共享服务
- centos7-vsftpd文件服务器
- Mysql的逻辑架构
- centos7-httpd服务器
- Linux文件 目录与权限
- 简易数据分析(三):Web Scraper 批量抓取豆瓣数据与导入已有爬虫
- 嵌入式Linux开发环境搭建 配置Ubuntu
- 简易数据分析(五):Web Scraper 翻页、自动控制抓取数量 & 父子选择器
- 【深度】韦东山:一文看看尽linux对中断处理的前世今生
- 嵌入式开发之交叉编译程序万能命令_以freetype为例
- Python-EEG处理和事件相关电位(ERP)
- 嵌入式Linux开发 配置网络
- 问号脸:为什么 Java 中 “1000==1000” 为 false,而 ”100==100“ 为 true?
- 【硬核】韦东山:使用freetype显示一行文字
- 动画函数封装