这不是关于资源事务算法(实现)的文章,只是研究资源事务协作过程中,各种资源如何参与事务的(这是借用artech关于事务的文章,不过这里主要还是指数据库)。这里稍微提一下,可能一个业务的完成,需要多个资源或者服务的协作,但是任何服务到达底层依然无外乎资源的访问,这是一个树形结构,而根节点即是业务发起者,所以下面谈到参与者的时候一般泛指资源参与事务。
对于单个资源,事务控制可以显式调用类似BeginTransaction开始事务,然后提交或者回滚;但是如果需要多个资源,他们每个都有自己的资源管理方式和事务控制方式,怎样让他们协作,类似于一个资源一样完整的完成一项业务呢。这里需要单独提出事务管理器概念,从artech文章可以看到,事务管理器负责协调资源以便提供事务支持,但是事务管理器只是使用资源相应的资源管理器来协调资源,而真正实现事务操作或者事务算法的实现都是依赖资源自身的构造。
在这里,自己的理解有些模棱两可。事务管理器的概念问题,因为每个资源都有自己一套事务管理的方式,了解数据库的人都知道,数据库本身都提供事务支持,也就是说资源管理器本身就具备事务管理能力。而且事务本身就是资源管理器的一部分。自己觉得这里提到的事务管理器是一个纯粹的事务协调控制器,即他会协调那些能够提供事务支持的资源以便在业务层面上提供事务支持,但是他不会具体管理资源本身的事务,而且使用每个资源提供的相应的事务控制方式来完成整体性事务。所以本文接下来提到的事务管理器更多的是指高层次的事务协调控制器。
对于单个资源的访问操作,使用资源管理器的事务控制能力基本可以胜任。从数据库里面可以明显的看到。对于多个资源的访问操作,就没那么简单了。首先其中的任何一个资源的访问操作(下面简称参与者),他在进入操作之前,会检查上下文中是否存在上下文事务,如果存在,即把资源相应的资源管理器注册到当前上下文里面,然后继续操作。这个过程中,任何参与者的提交和回滚都会影响到当前上下文事务的其他参与者,保证要不全部提交,要不全部回滚,具体的提交机制可能略有不同,这个可以参照artech为博客文章。
上面提到事务参与者在操作之前都会检查当前上下文事务,然后决定是否需要把自己的资源管理器注册到上下文事务当中。检查当前上下文事务容易,在C#里面,Transaction.Current即是当前上下文事务。至于注册资源管理器,Transaction.Current对于不同的资源管理器提供三个不同的注册函数,
EnlistDurable | 登记持久资源管理器以参与事务。 |
EnlistPromotableSinglePhase | 使用可提升的单阶段登记 (PSPE) 登记具有内部事务的资源管理器。 |
EnlistVolatile | 登记可变资源管理器以参与事务。 |
这里注册的资源管理器实际只是实现事务管理器相应的接口的类,具体的实现可以从函数的参数里面发现。从开源的sqlite和postsql的.net驱动里面可以看到,在打开连接时,他们即会完成这个操作,即检查上下文事务,然后注册相应的资源管理器。Sqlite的操作比较简单,postsql的略微复杂。从测试来看,sql server和oracle基本也是打开连接时进行这一个操作。
using (DbConnection con = factory.CreateConnection()) { con.ConnectionString = connectionString; con.Open(); using (TransactionScope scope = new TransactionScope()) { using (DbCommand com = con.CreateCommand()) { com.CommandText = "update t_tick set tick=111"; com.ExecuteNonQuery(); throw new ArgumentException("测试"); com.CommandText = "update t_tick set tick=222"; com.ExecuteNonQuery(); } scope.Complete(); } con.Close(); }
数据库原始值为0,而上面的代码测试结果是111不是0。
因为sqlite的实现资源管理器接口的类相对比较容易简单,先来看看他的实现,在连接打开函数Open里面有如下代码
if (Transactions.Transaction.Current != null && SQLiteConvert.ToBoolean(FindKey(opts, "Enlist", Boolean.TrueString)) == true) EnlistTransaction(Transactions.Transaction.Current);
而EnlistTransaction函数最重要的就是初始化SQLiteEnlistment,一个资源管理器接口(IEnlistmentNotification)实现类。在他的构造函数里面,他调用连接启动数据库事务,然后在提交和回滚里面嗲用数据库事务的提交和回滚。这是最简单的实现。在以前讨论Rhinio.Queues队列时,也有过类似的事务实现想法,现在看来似乎当时太激进了。但是毕竟这是纯粹的数据库事务,直接使用资源本身的事务实现未尝不可。代码不多,干脆这里附上:
namespace System.Data.SQLite
{
using System;
using System.Data;
using System.Data.Common;
using System.Transactions;
internal class SQLiteEnlistment : IEnlistmentNotification
{
internal SQLiteTransaction _transaction;
internal Transaction _scope;
internal bool _disposeConnection;
internal SQLiteEnlistment(SQLiteConnection cnn, Transaction scope)
{
_transaction = cnn.BeginTransaction();
_scope = scope;
_scope.EnlistVolatile(this, System.Transactions.EnlistmentOptions.None);
}
private void Cleanup(SQLiteConnection cnn)
{
if (_disposeConnection)
cnn.Dispose();
_transaction = null;
_scope = null;
}
#region IEnlistmentNotification Members
public void Commit(Enlistment enlistment)
{
SQLiteConnection cnn = _transaction.Connection;
cnn._enlistment = null;
try
{
_transaction.IsValid(true);
_transaction.Connection._transactionLevel = 1;
_transaction.Commit();
enlistment.Done();
}
finally
{
Cleanup(cnn);
}
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
if (_transaction.IsValid(false) == false)
preparingEnlistment.ForceRollback();
else
preparingEnlistment.Prepared();
}
public void Rollback(Enlistment enlistment)
{
SQLiteConnection cnn = _transaction.Connection;
cnn._enlistment = null;
try
{
_transaction.Rollback();
enlistment.Done();
}
finally
{
Cleanup(cnn);
}
}
#endregion
}
}
至于sqlite的数据库事务实现,即DbTransaction的实现,相对简单得多,即在响应的函数里面执行sqlite的事务开始、提交、回滚命令。这个跟postsql基本没有太大的出入,postsql的数据库事务的实现基本也是执行数据库响应的命令来实现。
对于postsql的相应的驱动实现,比这个复杂。他调用的是EnlistPromotableSinglePhase函数,实现的是可提升的单阶段登记 (PSPE) 登记具有内部事务的资源管理器。目前对于PSPE在postsql中的实现不是很了解。
待续。。。
引用
System.Data.SQLite (http://sqlite.phxsoftware.com/)
实现资源管理器(http://msdn.microsoft.com/zh-cn/library/ms229975%28v=VS.90%29.aspx)