参与事务的每个资源由资源管理器管理,其作由事务管理器协调。 协调是通过向通过事务管理器登记事务的订阅者提供的通知来完成的。
本主题介绍如何在事务中登记资源(或多个资源),以及不同类型的登记。 以单阶段和多阶段提交事务主题介绍如何在已登记的资源之间协调事务提交。
在事务中登记资源
资源若要参与事务,它必须在事务中进行登记。 该 Transaction 类定义一组方法,其名称以 Enlist 提供此功能开头。 不同的 Enlist 方法对应于资源管理器可能具有的不同类型的登记。 具体而言,你应当为易失性资源使用 EnlistVolatile 方法,为持久资源使用 EnlistDurable 方法。 资源管理器的持久性(或相反,波动性)是指资源管理器是否支持故障恢复。 如果资源管理器支持故障恢复,它会在阶段 1(准备)期间将数据持久化到稳定存储中,这样,如果资源管理器发生故障,它可以在恢复后重新参与事务,并根据从 TM 收到的通知执行适当的操作。 一般情况下,易失性资源管理器管理易失性资源(例如内存中的事务性哈希表),持久性资源管理器管理具有持久存储后盾的资源(例如,磁盘为存储后盾的数据库)。
为了简单起见,在根据资源的持久性支持决定是使用 EnlistDurable 还是 EnlistVolatile 方法后,应为资源管理器实现 IEnlistmentNotification 接口,从而将资源登记为参与两阶段提交 (2PC)。 有关 2PC 的更多信息,请参见以单阶段和多阶段提交事务。
单个参与者可以通过多次调用EnlistDurable和EnlistVolatile来登记多个协议。
持久登记
EnlistDurable 方法用于登记要作为持久资源参与事务的资源管理器。 预计如果持久资源管理器在事务中途关闭,则一旦重新上线,可以通过在所有参与但未完成第 2 阶段的事务中重新登记(使用 Reenlist 方法)来执行恢复,并在完成恢复处理后调用 RecoveryComplete。 有关恢复的详细信息,请参阅 执行恢复。
EnlistDurable 方法都将 Guid 对象作为它们的第一个参数。 事务管理器使用 Guid 将持久登记与特定的资源管理器关联。 因此,资源管理器必须确保即使在不同的资源管理器之间重启时也使用相同的 Guid 来识别自身,否则恢复可能会失败。
EnlistDurable 方法的第二个参数是一个引用,该引用指向资源管理器用于接收事务通知的对象。 您所使用的重载会向事务管理器通知资源管理器是否支持单阶段提交 (SPC) 优化。 大多数情况下,应实现 IEnlistmentNotification 接口来参与两阶段提交 (2PC)。 但是,如果要优化提交过程,可以考虑实现 SPC 的 ISinglePhaseNotification 接口。 有关 SPC 的更多信息,请参见以单阶段和多阶段提交事务和使用单阶段提交和可提升的单阶段通知进行优化。
第三个参数是一个 EnlistmentOptions 枚举,其值可以是 None 或 EnlistDuringPrepareRequired。 如果将该值设置为 EnlistDuringPrepareRequired,则这种登记类型可在从事务管理器接收到准备通知时登记附加资源管理器。 但是,您应清楚这种登记类型不适合执行单阶段提交优化。
不稳定征募
管理可变资源(如缓存)的参与者应使用 EnlistVolatile 该方法登记。 此类对象可能无法获取事务的结果,或恢复在系统故障后参与的任何事务的状态。
如前面所述,资源管理器在管理内存中的可变资源时应进行可变登记。 使用 EnlistVolatile 的好处之一是,它不会强制不必要的事务升级。 有关事务升级的详细信息,请参阅 事务管理升级 主题。 登记可变性意味着事务管理器处理登记的方式与事务管理器对资源管理器的预期行为之间存在差异。 这是因为易失性资源管理器不执行恢复。 方法 EnlistVolatile 不采用 Guid 参数,因为易失性资源管理器不执行恢复,并且不会调用 Reenlist 需要参数 Guid的方法。
与持久登记一样,无论使用哪种重载方法进行登记,都会向事务管理器指示资源管理器是否支持单阶段提交优化。 由于易失性资源管理器无法执行恢复,因此在准备阶段,不会为易失性登记写入任何恢复信息。 因此,调用该方法 RecoveryInformation 将生成一个 InvalidOperationException。
以下示例演示如何使用 EnlistVolatile 该方法将此类对象登记为事务中的参与者。
static void Main(string[] args)
{
try
{
using (TransactionScope scope = new TransactionScope())
{
//Create an enlistment object
myEnlistmentClass myEnlistment = new myEnlistmentClass();
//Enlist on the current transaction with the enlistment object
Transaction.Current.EnlistVolatile(myEnlistment, EnlistmentOptions.None);
//Perform transactional work here.
//Call complete on the TransactionScope based on console input
ConsoleKeyInfo c;
while(true)
{
Console.Write("Complete the transaction scope? [Y|N] ");
c = Console.ReadKey();
Console.WriteLine();
if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
{
scope.Complete();
break;
}
else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
{
break;
}
}
}
}
catch (System.Transactions.TransactionException ex)
{
Console.WriteLine(ex);
}
catch
{
Console.WriteLine("Cannot complete transaction");
throw;
}
}
class myEnlistmentClass : IEnlistmentNotification
{
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("Prepare notification received");
//Perform transactional work
//If work finished correctly, reply prepared
preparingEnlistment.Prepared();
// otherwise, do a ForceRollback
preparingEnlistment.ForceRollback();
}
public void Commit(Enlistment enlistment)
{
Console.WriteLine("Commit notification received");
//Do any work necessary when commit notification is received
//Declare done on the enlistment
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
Console.WriteLine("Rollback notification received");
//Do any work necessary when rollback notification is received
//Declare done on the enlistment
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
Console.WriteLine("In doubt notification received");
//Do any work necessary when in doubt notification is received
//Declare done on the enlistment
enlistment.Done();
}
}
Public Shared Sub Main()
Try
Using scope As TransactionScope = New TransactionScope()
'Create an enlistment object
Dim myEnlistmentClass As New EnlistmentClass
'Enlist on the current transaction with the enlistment object
Transaction.Current.EnlistVolatile(myEnlistmentClass, EnlistmentOptions.None)
'Perform transactional work here.
'Call complete on the TransactionScope based on console input
Dim c As ConsoleKeyInfo
While (True)
Console.Write("Complete the transaction scope? [Y|N] ")
c = Console.ReadKey()
Console.WriteLine()
If (c.KeyChar = "Y") Or (c.KeyChar = "y") Then
scope.Complete()
Exit While
ElseIf ((c.KeyChar = "N") Or (c.KeyChar = "n")) Then
Exit While
End If
End While
End Using
Catch ex As TransactionException
Console.WriteLine(ex)
Catch
Console.WriteLine("Cannot complete transaction")
Throw
End Try
End Sub
End Class
Public Class EnlistmentClass
Implements IEnlistmentNotification
Public Sub Prepare(ByVal myPreparingEnlistment As PreparingEnlistment) Implements System.Transactions.IEnlistmentNotification.Prepare
Console.WriteLine("Prepare notification received")
'Perform transactional work
'If work finished correctly, reply with prepared
myPreparingEnlistment.Prepared()
End Sub
Public Sub Commit(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Commit
Console.WriteLine("Commit notification received")
'Do any work necessary when commit notification is received
'Declare done on the enlistment
myEnlistment.Done()
End Sub
Public Sub Rollback(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Rollback
Console.WriteLine("Rollback notification received")
'Do any work necessary when rollback notification is received
'Declare done on the enlistment
myEnlistment.Done()
End Sub
Public Sub InDoubt(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.InDoubt
Console.WriteLine("In doubt notification received")
'Do any work necessary when in doubt notification is received
'Declare done on the enlistment
myEnlistment.Done()
End Sub
End Class
优化性能
Transaction 类还提供了 EnlistPromotableSinglePhase 方法来登记可提升的单阶段登记 (PSPE)。 这使持久资源管理器 (RM) 可承载和“拥有”以后可在需要时升级为由 MSDTC 进行管理的事务。 有关此内容的更多信息,请参见使用单阶段提交和可提升的单阶段通知进行优化。