有一次和人谈起关于事务的话题,谈到怎样的资源才能事务型资源。除了我们经常使用的数据库、消息队列、事务型文件系统(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>就不能体现出来。