.NET世界中的System.Transactions基础结构


您是否看到过在C#中using (var scope = new TransactionScope(TransactionScopeOption.Required))的构造? 这意味着在using块中运行的代码在事务中,并且退出该块后,更改将被提交或回滚。 在您开始深入研究之前,这听起来是可以理解的。 随着您的深入研究,它变得“奇怪而奇怪”。 无论如何,当我对TransactionScope类以及通常对.NET事务更加熟悉时,出现了很多问题。

什么是TransactionScope类? 一旦我们使用using (var scope = new TransactionScope())构造,程序中的所有内容都会立即变为事务性的吗? 什么是“资源管理器”和“事务管理器”? 我可以编写自己的资源管理器,如何将其“连接”到创建的TransactionScope实例? 什么是分布式事务,并且SQL Server或Oracle数据库中的分布式事务与分布式.NET事务相同吗?

在本出版物中,我尝试收集有助于查找这些问题答案并建立对.NET世界中事务的理解的材料。


引言


什么是交易,它们解决什么问题?


这里讨论的事务是将系统从一种可接受的状态转移到另一种可接受的状态的操作,即使发生不可预见的情况,也可以保证不会使系统处于不可接受的状态。 通常情况下,这些可接受的条件是哪种情况取决于上下文。 在这里,我们将考虑一个可接受的情况,其中我们处理的数据是不可或缺的。 可以理解,构成事务的更改可以一起提交,也可以不提交。 另外,对一个事务的更改可以与另一个事务对系统所做的更改隔离。 交易的基本要求用缩写ACID表示。 对于初次接触他们的人,适合在Wikipedia上发表文章


交易的经典示例是两个帐户之间的资金转移。 在这种情况下,不从第一号帐户中提取资金而不将其存入第二号帐户是不可接受的,这与在不从第一号帐户中提取而向第二号帐户中进行存款的方式相同。换句话说,我们希望这两种操作都可以同时进行-立即执行。 如果其中之一失败,则不应执行第二个操作。 您可以将此原则称为“全有或全无”。 而且,期望即使在诸如电力中断之类的系统故障的情况下,也要同步地执行操作,即,一旦恢复后系统可用,我们就认为系统处于可接受的状态。

用数学术语来说,我们可以说关于系统,我们肯定要保留一个不变性。 例如,两个帐户上的金额:交易(汇款)之后,金额必须保持与之前相同。 顺便说一下,在经典的汇款示例中,会计也出现了-交易概念自然产生的主题领域。

我们以在两个帐户之间转移资金为例。 第一张图片显示了从1号帐户到2号帐户的50卢布转帐成功完成的情况。 绿色表示系统处于可接受状态(数据已完成)。


现在,假设转账是在交易外部进行的,并且从第1号帐户中提取了资金之后,发生了故障,因此提取的资金没有记入第2号帐户中。系统将处于不可接受的状态(红色)。


如果提款和贷记操作之间发生错误,但转账是一项交易的一部分,则提款操作将被取消。 结果,系统将保持其原始可接受状态。


我将从公司的经验中举例说明一些对交易有用的情况:会计处理商品(会计处理某些商店中和途中各种类型的商品数量),会计存储资源(会计处理某种类型的商品所占房间的空间,免费提供商品放置,员工和自动存储系统每天可以移动的商品数量)。

违反数据完整性时出现的问题是显而易见的。 系统提供的信息不仅会变成虚假的信息,还会与现实失去联系,变成胡扯。

这里考虑什么交易


交易提供的好处是已知的。 那么,为了保持数据完整性,是否需要一个关系数据库,因为那是进行事务的地方? 不完全是 上面已经说过,交易的概念取决于上下文,现在我们将在讨论信息系统时简要考虑可以讨论哪些交易。

首先,我们将主题域交易(业务交易)和系统交易的概念分开。 第二个可以在不同的地方以不同的方式实现。

让我们从最高级别-主题领域开始。 感兴趣的人可能会声明存在某些可接受的状态,并且他不想看到这些状态之外的信息系统。 我们不会提供其他示例:在各个帐户之间转帐是合适的。 我们仅阐明,转移不一定是两个银行客户的结算帐户之间的资金转移。 当帐户应反映组织资金的来源和目的,而转移应反映这些来源和目的的资金分配变化时,会计任务同样重要。 这是一个主题域交易的示例。

现在,让我们看看实现系统事务的最常见和最有趣的示例。 在系统交易中,各种技术手段提供了主题领域的要求。 这种经过验证的经典解决方案是关系DBMS事务 (第一个示例)。 现代的数据库管理系统(关系型和非关系型)都提供了一种事务处理机制,该机制使您可以保存(提交)在指定的工作期内进行的所有更改,或者丢弃(回滚)它们。 当使用这种机制,即从组成一个领域的交易的一个帐户中取钱并贷记到另一个帐户中的操作时,DBMS装置将被组合成一个系统交易,或者一起执行,或者根本不执行。

当然,不必使用DBMS。 粗略地说,您通常可以使用自己喜欢的编程语言来实现DBMS事务处理机制,并享受现有工具的不稳定和错误的模拟。 但是您的“自行车”可以针对主题领域的特定情况进行优化。

还有更多有趣的选项。 现代工业编程语言(首先是C#和Java)提供了专门设计用于组织涉及完全不同的子系统(而不仅仅是DBMS)的事务的工具。 在本出版物中,我们将其称为此类交易软件。 对于C#,这些是来自System.Transactions命名空间的事务 (第二个示例),下面将对其进行描述。

在继续进行System.Transactions事务之前,不能不提及另一种有趣的现象。 System.Transactions工具允许程序员独立实现程序化事务存储 。 在这种情况下,影响系统状态的程序操作(在经典命令式编程语言的情况下,这是一个赋值操作)默认包含在事务中,这些事务可以以与DBMS事务几乎相同的方式进行提交和回滚。 通过这种方法,大大减少了使用同步机制的需求(在C# lock ,在Java synchronized )。 这个想法的进一步发展是在平台级别受支持的软件事务存储 (第三个示例)。 可以用一种超越其工业实用性的语言-Clojure来发现这样的奇迹。 对于工农语言,有一些插件库可提供程序化事务存储的功能。

系统事务可以包括多个信息系统,在这种情况下,它们将变得分布式。 分布式既可以是DBMS事务,也可以是软件。 这完全取决于特定交易工具支持的功能。 在相应的部分中讨论了更详细的分布式事务。 我将提供一张图片,以使您更容易理解所讨论的主题。


TL; DR部分


有一些过程由应用于系统的几个不可分割的(原子)操作组成,通常情况下不一定是信息性的。 当数据完整性受到损害时,每个不可分割的操作都会使系统处于无法接受的状态。 例如,如果两个帐户之间的汇款是通过两个不可分割的操作来表示的:从帐户1提取资金并记入帐户2,那么这些操作中只有一个会破坏数据的完整性。 金钱要么消失在茫茫荒野中,要么出现在茫茫荒野中。 事务结合了不可分割的操作,因此它们全部一起执行(当然,必要时顺序执行)或根本不执行。 我们可以讨论领域交易和通常实现领域交易的技术系统中的交易。

基于System.Transactions的事务


这是什么


在.NET世界中,有一个由事务管理平台的创建者设计的软件框架。 从事务程序员的角度来看,此框架由System.Transactions命名空间的TransactionScopeTransactionScopeOptionTransactionScopeAsyncFlowOptionTransactionOptions System.Transactions 。 如果我们谈论.NET Standard,那么所有这些都可以从2.0版开始使用。

System.Transactions命名空间中的事务基于The Open Group的X / Open XA标准 。 该标准引入了下面讨论的许多术语,最重要的是,它描述了分布式事务,本出版物的特殊部分也对此进行了介绍。 其他平台(例如Java)中的软件事务实现基于相同的标准。

C#程序员的典型事务用例如下:

 using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { // -  ,    . scope.Complete(); } 

using块内部是执行工作的代码,必须一起提交或取消其结果。 此类工作的典型示例是读写数据库或从队列发送和接收消息。 当控件离开using块时,将提交事务。 如果删除Complete呼叫,则事务将回滚。 很简单

事实证明,在事务回滚期间,在using块内进行的所有操作都将被取消吗? 如果我为变量分配了其他值,那么此变量将恢复旧值吗? 当我第一次看到类似的设计时,我是这么认为的。 实际上,当然,并非所有更改都会回滚,而只是非常特殊的更改。 如果所有更改都已回滚,则将是上述软件事务存储。 现在,让我们看看这些可以参与基于System.Transactions程序事务的特殊更改是什么。

资源经理


为了使某些东西支持基于System.Transactions事务,必须拥有当前正在进行的事务的信息,并且必须在事务参与者的某些注册表中对其进行注册。 您可以通过检查System.Transactions.Transaction类的静态属性Current获得有关事务工作是否正在进行的信息。 如果之前尚未设置,则输入上述类型的using块只会设置此属性。 要注册为交易参与者,可以使用Transaction.Enlist Smth类型的方法。 另外,您需要实现这些方法所需的接口。 资源管理器-就是这样的“东西”,它支持与System.Transactions事务进行交互(下面给出了更具体的定义)。

什么是资源管理器? 如果我们使用C#和DBMS(例如SQL Server或Oracle数据库)一起工作,则通常使用适当的驱动程序,它们是管理资源。 在代码中,它们由类型System.Data.SqlClient.SqlConnectionOracle.ManagedDataAccess.Client.OracleConnection他们还说 ,MSMQ支持基于System.Transactions事务。 在Internet上获得的知识和示例的指导下,您可以创建自己的资源管理器。 下一节将给出最简单的示例。

除了资源管理器之外,我们还必须具有事务管理器,该事务管理器将监视事务并及时将订单下达给资源管理器。 根据事务中涉及的资源管理器(它们具有什么特征以及它们位于何处),将不同的事务管理器连接到工作。 在这种情况下,适当版本的选择是自动的,不需要程序员的干预。

更具体地说,资源管理器是实现特殊System.Transactions.IEnlistmentNotification接口的类的实例。 按照客户端的指示,使用静态属性System.Transactions.Transaction.Current将类实例注册为事务的参与者。 随后,事务管理器根据需要调用指定接口的方法。


显然,在运行时,事务中涉及的资源管理器集合可能会更改。 例如,进入using块之后,我们可以先在SQL Server中执行某项操作,然后在Oracle Database中执行某项操作。 根据这组资源管理器,确定使用的事务管理器。 更准确地说,所使用的事务协议由资源管理器集合确定,并且支持该事务的事务管理器基于该协议确定。 当我们谈论分布式事务时,我们将在后面讨论事务协议。 更改事务中涉及的资源管理器时,在运行时自动选择合适的事务管理器的机制称为事务促进。

资源管理器的类型


资源管理器可以分为两大类:持久性和可变性。

耐用资源管理器-即使信息系统不可用(例如,计算机重新启动时),也支持事务的资源管理器。 易失性资源管理器-如果信息系统不可用,则不支持事务的资源管理器。 不一致的资源管理器仅支持内存中的事务。

经典的长期资源管理器是DBMS(或软件平台的DBMS驱动程序)。 无论发生什么事情-至少是操作系统中的故障,至少是电源中断-DBMS在恢复工作状态后都将保证数据的完整性。 为此,您当然要付出一些不便,但是在本文中我们将不考虑它们。 非持久资源管理器的一个示例是上述软件事务存储。

使用TransactionScope


创建TransactionScope类型的对象时TransactionScope可以指定一些参数。

首先,有一个设置告诉运行时它需要什么:

  1. 使用当前已经存在的事务;
  2. 确保创建一个新的。
  3. 相反,在事务外部的using块内执行代码。

System.Transactions.TransactionScopeOption枚举负责所有这一切。

其次,您可以设置事务隔离级别。 此参数使您可以在更改的独立性和速度之间找到折衷方案。 最独立的级别-可序列化-确保在任何情况下都不会在另一个事务中看到尚未提交的更改。 当同时运行的事务可能相互影响时,下一个级别都会增加一个这样的特定情况。默认情况下,事务在可序列化级别打开,这可能会令人不愉快(例如,请参见此注释)。

在创建过程中设置事务隔离级别TransactionScope对于资源管理器来说是建议。他们甚至可能不支持列出的所有级别System.Transactions.IsolationLevel。另外,应该记住,当使用连接池与数据库一起使用时,已更改事务隔离级别的连接将在返回到池时保留该级别。现在,当程序员从池中接收到此连接并依靠默认值时,他将观察到意外的行为。

典型工作场景cTransactionScope关于“ Habr”的文章充分介绍了很多陷阱(即嵌套事务)

软件交易适用性


应该说,在几乎所有商业运营的信息系统中,都会启动一些过程,这些过程可能导致系统进入无法接受的状态。因此,可能有必要控制这些过程,找出系统的当前状态是否可以接受,如果不能,请对其进行恢复。软件交易-用于将系统维持在可接受状态的现成工具。

在每种情况下,考虑成本是有建设性的:

  1. 将流程集成到软件交易基础架构中(这些流程还需要注意TransactionScope许多其他事项);
  2. 维护此基础结构(例如,租用带Windows的设备的设备的成本);
  3. 员工培训(因为.NET事务的主题不常见)。

我们一定不要忘记,可能需要交易过程将其进度报告给“外部世界”,例如,保留交易外部的行动日志。

显然,拒绝软件交易将需要创建或实施其他一些维护数据完整性的方法,这也将具有其价值。最后,在某些情况下,违反数据完整性的情况非常罕见,以至于通过手术干预恢复系统的可接受状态要比维护自动恢复机制容易。

善变的资源管理器的一个例子


现在,让我们看一个不支持从系统故障中恢复的简单资源管理器的示例。我们将有一块软件事务性内存,用于存储一些可以读取和写入的值。在没有事务的情况下,此块的行为类似于普通变量,在有事务的情况下,它存储初始值,可以在回滚事务后将其恢复。这样的资源管理器的代码如下所示:

 internal sealed class Stm<T> : System.Transactions.IEnlistmentNotification { private T _current; private T _original; private bool _enlisted; public T Value { get { return _current; } set { if (!Enlist()) { _original = value; } _current = value; } } public Stm(T value) { _current = value; _original = value; } private bool Enlist() { if (_enlisted) return true; var currentTx = System.Transactions.Transaction.Current; if (currentTx == null) return false; currentTx.EnlistVolatile(this, System.Transactions.EnlistmentOptions.None); _enlisted = true; return true; } #region IEnlistmentNotification public void Commit(System.Transactions.Enlistment enlistment) { _original = _current; _enlisted = false; } public void InDoubt(System.Transactions.Enlistment enlistment) { _enlisted = false; } public void Prepare(System.Transactions.PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); } public void Rollback(System.Transactions.Enlistment enlistment) { _current = _original; _enlisted = false; } #endregion IEnlistmentNotification } 

可以看出,唯一的正式要求是接口的实现System.Transactions.IEnlistmentNotification。在有趣的方法中Enlist(不是一部分System.Transactions.IEnlistmentNotification)和Prepare。该方法Enlist仅检查给定代码是否在事务框架内工作,如果是,则将其类的实例注册为非常量资源管理器。Prepare提交更改之前,事务管理器将调用该方法。我们的资源管理器通过调用方法来表示他准备提交System.Transactions.PreparingEnlistment.Prepared

以下代码显示了使用资源管理器的示例:

 var stm = new Stm<int>(1); using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { stm.Value = 2; scope.Complete(); } 

如果您在退出该块后立即using读取该属性stm.Value,则期望的值在那里2并且,如果您删除该调用scope.Complete,则事务将回滚,并且属性stm.Value1在事务开始之前设置下图显示

了处理事务时的简化呼叫顺序System.Transactions


可以看出,在该示例中,并未考虑基础设施提供的所有可能性System.Transactions在下一节中我们熟悉事务协议和分布式事务之后,我们将更全面地考虑它们。

TL; DR部分


程序员可以使用类TransactionScope在现有或新事务中执行某些代码。当且仅当在类的现有实例上TransactionScope调用方法时,才提交事务Dispose,即使该方法之前被调用Complete。程序员可以指示他是要开始新事务,利用现有事务还是相反地在现有事务之外执行代码。事务中仅涉及资源管理器-实现某些功能的软件组件。资源管理器可以是长期的(从系统故障中恢复)和间歇的(不恢复)。 DBMS是长期存在的资源管理器的一个示例。资源管理器由事务管理器进行协调,事务管理器是一种运行时自动选择的软件组件,无需程序员的参与。

资源管理器不一致是System.Transactions.IEnlistmentNotification在方法中实现接口的类Prepare确认其准备提交更改,或者相反,表示已回滚更改。当调用方对资源管理器执行某些操作时,它将检查事务是否现在已打开,如果事务已打开,则使用方法注册System.Transactions.Transaction.EnlistVolatile

分布式交易


这是什么


分布式事务涉及多个信息子系统(实际上,并非所有事情都那么简单,更多内容请参见下文)。可以理解,分布式事务中涉及的所有系统中的更改都必须提交或回滚。

上面介绍了实现事务的各种方法:DBMS,基础结构System.Transactions和内置在平台中的程序化事务存储器。这些工具也可以提供分布式事务。例如,在Oracle数据库中,在单个事务中更改(并实际读取)多个数据库中的数据会自动将其转变为分布式数据。接下来,我们将讨论软件分布式事务,其中可能包括异构资源管理器。

交易协议


事务协议是一组原则,事务中涉及的应用程序通过这些原则进行交互。在.NET世界中,最常见的是以下协议。

轻巧。使用的持久性资源管理器不超过一个。所有事务交互都发生在同一应用程序域内,或者资源管理器支持升级和单阶段提交(实现IPromotableSinglePhaseNotification)。

OleTx。允许多个应用程序域和多台计算机之间的互操作性。您可以使用许多持久性资源管理器。所有参与的计算机都必须运行Windows。使用远程过程调用(RPC)。

WS-AT。允许多个应用程序域和多台计算机之间的互操作性。您可以使用许多持久性资源管理器。参与的计算机可能正在运行各种操作系统,而不仅仅是Windows。使用超文本传输​​协议(HTTP)。

上面已经指出,当前的交易协议会影响交易管理器的选择,而交易中涉及的控制资源的特性也会影响协议的选择。现在我们列出了著名的交易管理器。

轻量级交易管理器(LTM)。在.NET Framework 2.0和更高版本中引入。使用轻量级协议管理事务。

内核事务管理器(KTM)。在Windows Vista和Windows Server 2008中引入。使用轻量级协议管理事务。它可以在Windows Vista和Windows 2008上调用事务性文件系统(TxF)和事务性注册表(TxR)。

分布式事务处理协调器(MSDTC)。使用OleTx和WS-AT协议管理事务。

还应该记住,某些资源管理器不支持列出的所有协议。例如,MSMQ和SQL Server 2000不支持轻量级,因此涉及MSMQ或SQL Server 2000的事务将由MSDTC管理,即使它们是唯一的参与者。从技术上讲,此限制来自以下事实:指定的资源管理器当然实现了接口System.Transactions.IEnlistmentNotification不要实现该接口System.Transactions.IPromotableSinglePhaseNotification它包含一个方法Promote,运行时在必要时调用该方法以切换到更严格的事务管理器。

分布式事务的概念的歧义现在应该变得显而易见。例如,您可以将分布式事务定义为它参与的事务:

  1. 任何资源管理器中至少有两个;
  2. 任意可变的资源管理器和至少两个长期存在的资源管理器;
  3. 必须在不同计算机上的任何资源管理器中至少有两个。

因此,最好总是弄清楚涉及哪些特定事务。

在这种情况下,主要讨论MSDTC。它是Windows中管理分布式事务的软件组件。在路径“计算机-我的电脑-分布式事务处理协调器-本地DTC”之后,可以在“组件服务”实用程序中找到用于配置和监视事务的图形界面。


要进行配置,请在“本地DTC”节点的上下文菜单中选择“属性”项,并要监视分布式事务,请在中央面板中选择“事务统计”项。

双相固定


如果有多个资源管理器参与该事务,则他们的工作结果可能会有所不同:例如,其中一个成功完成,他准备提交更改,另一个准备出错,他将回滚更改。但是,分布式事务的本质在于,事务中涉及的所有控制资源的更改要么一起提交,要么回滚。因此,在这种情况下,通常使用两阶段固定方案。

通常,该协议的本质如下。在第一阶段事务中涉及的资源管理器准备足以从故障中恢复的信息(如果它是长期资源管理器),并足以确保提交成功完成。从技术角度来看,资源管理器通过调用方法System.Transactions.PreparingEnlistment.Prepared中的方法来发出信号,表示已完成第一阶段Prepare。或者资源管理器可以通过调用方法通知您更改已回滚ForceRollback

当参与事务的所有资源管理器都“投票”时,即,他们通知事务管理器是否要提交或回滚更改时,第二阶段开始。此时,指示资源管理器提交其更改(如果所有参与者都投票赞成解决问题)或拒绝更改(如果至少一位参与者投票赞成回退)。从技术上讲,这表示为方法的调用,资源管理器实现的方法Commit以及Rollback资源管理器在其中调用的方法System.Transactions.Enlistment.Done

资源管理器也可以System.Transactions.Enlistment.Done在第一阶段调用该方法。在这种情况下,可以理解的是他将不会进行任何更改(例如,仅用于阅读),并且不会参与第二阶段。在Microsoft上了解有关两阶段提交的更多信息

如果事务管理器与至少一个资源管理器之间的连接丢失,则事务将被冻结(“不确定”,有疑问)。事务管理器通过调用方法InDoubt,将可以适当响应的此事件通知可用的资源管理器。

仍然存在三相固定及其修改,并具有其优点和缺点。三相提交协议不太常见,可能是因为它要求交互子系统之间的通信成本更高。

System.Transactions接口上的备忘单


很难。为了解决问题,我将简要描述System.Transactions创建资源管理器所需的主要名称空间接口。这是一个类图。



IEnlistmentNotification。资源管理器实现此接口。事务管理器按以下顺序调用已实现的方法。在第一阶段中,他调用了该方法Prepare(除非星星聚在一起调用该方法ISinglePhaseNotification.SinglePhaseCommit,如下一段所述)。在这种方法下,资源管理器保存了从故障中恢复所必需的信息,为他方面的最后一次更改准备作了准备,并投票以提交或回滚更改。如果有来自第二阶段,根据资源和控制权交易的投票结果控制的有效性是三种方法之一:CommitInDoubtRollback

ISinglePhaseNotification。如果资源管理器希望为事务管理器提供减少第二提交阶段来优化执行的机会,则资源管理器将实现此接口。如果事务管理器仅看到一个资源管理器,则在第一个提交阶段,他尝试调用资源管理器的方法SinglePhaseCommit(代替IEnlistmentNotification.Prepare),从而排除投票和过渡到第二阶段。这种方法具有优点和缺点,这是Microsoft 在此处最清楚地写到的

ITransactionPromoter。资源管理器实现此接口(不仅直接实现,而且通过接口实现)IPromotableSinglePhaseNotification),如果他想让交易经理即使在远程调用时也能够遵守轻量级协议,直到出现其他需要协议复杂的情况。当您需要使协议复杂时,该方法将被调用Promote

IPromotableSinglePhaseNotification。资源管理器实现此接口的顺序是:首先实现该接口ITransactionPromoter;其次,以便事务管理器可以使用单阶段提交,调用方法IPromotableSinglePhaseNotification.SinglePhaseCommitIPromotableSinglePhaseNotification.Rollback。事务管理IPromotableSinglePhaseNotification.Initialize器以简化的方式调用一种方法来标记资源管理器的成功注册。或多或少,这可以从Microsoft文档中了解

让我们再看一点System.Transactions.Enlistment和他的继承人。事务管理器在调用资源管理器实现的接口方法时提供这种实例。



入伍。资源管理器可以调用这种类型的单个方法- Done,以表示已成功完成其工作。

准备入伍。在第一个提交阶段使用这种类型的实例,资源管理器可以发出他打算提交或回滚更改的意图。寿命长的资源管理器还可以获得从系统故障中恢复所需的信息。

SinglePhaseEnlistment。使用这种类型的实例,资源管理器可以使用简化方案(单阶段提交)将有关其工作结果的信息传输到事务管理器。

软件分布式交易限制和替代方案


对Internet上意见的简短研究表明,在许多领域中,分布式事务已过时。例如,看看这个恶意评论此处简要提到的批评的主要目的是分布式事务的同步(阻塞)性质。如果用户在组织分布式事务的处理过程中发送了请求,则只有在(成功或有错误)事务中包含的所有子系统完成工作后,他才会收到响应。同时,有一种观点得到了研究的支持,即两阶段提交协议显示出较差的性能,特别是随着事务中涉及的子系统数量的增加,例如,在关于“哈布雷”的出版物

如果系统的创建者希望尽快将答案返回给用户,将数据的协调推迟到以后,那么其他解决方案将更适合他。在Brewer定理(CAP定理的上下文中,我们可以说分布式事务适用于数据一致性比可用性更重要的情况。

使用软件分布式事务还有其他实际限制。例如,通过实验确定了使用OleTx协议的分布式事务不应跨越网络域。无论如何,让他们工作的长期尝试都没有成功。此外,据透露,Oracle数据库的多个实例(分布式数据库事务)之间的交互对软件分布式事务的适用性施加了严格的限制(同样,启动失败)。

分布式事务有哪些替代方案?首先,我必须说,没有技术交易(正常的,不分散的)将很难做到。系统中可能有一些过程会暂时破坏数据完整性,因此有必要以某种方式对这些过程进行监督。同样,就主题领域而言,可能会出现一个概念,其中包括一个由不同技术系统中的一组过程实现的过程,该过程应在完整数据领域中开始和结束。

转向分布式事务的替代方案,我们可以注意到基于消息服务的解决方案,例如RabbitMQ和Apache Kafka。在有关“哈布雷”的出版物中,考虑了四种此类解决方案:

  1. , , ;
  2. , (Transaction Log Tailing);
  3. , ;
  4. (Event Sourcing).

另一种选择是Saga模板。它涉及具有其本地事务的一系列子系统。工作完成后,每个系统(独立地或在协调员的帮助下)调用以下内容。对于每个事务,都有一个对应的取消事务,并且子系统可以代替先前的子系统取消对控制的转移,而可以取消先前的子系统。在“Habré”上,有一些关于“ Saga”模板的好文章。例如,该出版物提供了有关在微服务中维护ACID原理的一般信息,并且本文详细介绍了使用协调器实现Saga模板的示例。

在我们公司中,某些产品通过WCF成功使用了软件分布式事务,但是还有其他选择。有一次,当我们尝试与具有分布式事务的新系统交朋友时,我们遇到了许多问题,包括与上述限制的冲突以及在更新软件基础结构方面的并行问题。因此,在缺乏资源来进行另一项资本决策的情况下,我们采用了以下策略。在任何情况下,被叫方都会捕获更改,但是请注意它们处于草稿状态,因此这些更改尚未影响被叫系统的操作。然后,调用者通过分布式事务完成工作时,DBMS激活被调用系统所做的更改。这样代替软件分布式事务,我们使用了分布式DBMS事务,在这种情况下,事实证明它更加可靠。

在.NET Core中是吗?


在.NET Core中(甚至在.NET Standard中),所有用于组织事务和创建自己的资源管理器的必要类型。不幸的是,在.NET Core中,基于事务System.Transactions的局限性很严重:它们仅适用于轻量级协议。例如,如果在代码中使用了两个持久资源管理器,则在运行时,环境将在调用第二个管理器后立即引发异常。

事实上,他们试图使.NET Core独立于操作系统,因此排除了与事务管理器(例如KTM和MSDTC)的链接,即需要它们来支持具有指定属性的事务。交易管理器的连接可能会以插件的形式实现,但是到目前为止,这已经用干草叉编写了,所以您还不能依赖.NET Core中分布式交易的工业用途。

根据经验,您可以通过编写相同的代码,在不同平台上编译和运行它来验证.NET Framework和.NET Core中分布式事务中的差异。

此类代码的示例依次调用SQL Server和Oracle数据库。

 private static void Main(string[] args) { using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { MsSqlServer(); Oracle(); scope.Complete(); } } private static void Oracle() { using (var conn = new Oracle.ManagedDataAccess.Client.OracleConnection("User Id=some_user;Password=some_password;Data Source=some_db")) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1"; cmd.ExecuteNonQuery(); } conn.Close(); } } private static void MsSqlServer() { var builder = new System.Data.SqlClient.SqlConnectionStringBuilder { DataSource = "some_computer\\some_db", UserID = "some_user", Password = "some_password", InitialCatalog = "some_scheme", Enlist = true, }; using (var conn = new System.Data.SqlClient.SqlConnection(builder.ConnectionString)) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1"; cmd.ExecuteNonQuery(); } conn.Close(); } } 

Build-ready项目在GitHub上

运行.NET Core示例失败。引发异常的位置和类型取决于DBMS调用的顺序,但是无论如何,此异常表示无效的事务操作。如果此时正在运行MSDTC,则将成功运行.NET Framework示例。但是,在MSDTC的图形界面中,您可以观察到分布式事务的注册。

分布式事务和WCF


Windows Communication Foundation(WCF)是用于组织和调用网络服务的.NET框架。与更流行的REST和ASP.NET Web API方法相比,它具有自己的优点和缺点。 WCF是.NET事务的好朋友,在.NET Framework的世界中,使用它方便地组织在客户端和服务之间分布的事务。

在.NET Core中,此技术仅在客户端起作用,即,您不能创建服务,而只能引用现有的服务。但是,这不是很重要,因为如上所述,对于.NET Core中的分布式事务,一切都做得不好。

WCF的工作方式

对于不熟悉WCF的读者,这里是有关此技术实际应用的最短的背景信息。上下文-称为客户端和服务的两个信息系统。客户端在运行时访问支持该客户端关注的服务的另一个信息系统,并要求执行某些操作。然后将管理返回给客户端。

要在WCF上创建服务,通常需要编写一个接口,该接口描述要创建的服务的合同,以及一个实现此接口的类。类和接口标记有特殊的WCF属性,这些属性将它们与其他类型区分开来,并指定了服务发现和调用期间的某些行为细节。这些类型包装在可用作服务器的内容中(例如,在IIS所针对的DLL中),并由配置文件(有选项)补充,其中指出了服务实现的详细信息。启动后,可以例如在网络地址上访问该服务;在Internet浏览器中,您可以看到请求的服务实现的合同。

想要访问现有WCF服务的程序员使用控制台实用程序或开发环境中内置的图形界面,以C#(或另一种受支持的语言)形式形成与服务地址处的服务合同相对应的类型。具有获得的类型的文件包含在客户端应用程序项目中,然后程序员使用服务接口中包含的相同术语,享受进度(静态键入)的好处。此外,客户的配置文件指定了被叫服务的技术特征(也可以在代码中进行配置,而无需配置文件)。

WCF支持各种类型的传输,加密和其他更细微的技术参数。它们中的大多数由“绑定”(Binding)的概念统一在一起。WCF服务有三个重要参数:

  1. 它可用的地址;
  2. 装订
  3. 合同(接口)。

所有这些参数都在服务和客户端配置文件中设置。

在我们公司中,WCF(带有和不带有分布式事务的WCF)被广泛用于已实现的产品中,但是,鉴于流行趋势,其在新产品中的使用仍然是个问题。

如何在WCF中启动分布式事务

为了在WCF中启动基于事务的事务System.Transactions,程序员需要在代码中设置几个属性,确保所使用的绑定支持分布式事务,它在客户端和服务中transactionFlow="true"均已编写,并且适当的事务管理器正在所有涉及的计算机上运行(最有可能的)。 ,则为MSDTC)。

分布式事务绑定:NetTcpBinding,NetNamedPipeBinding,WSHttpBinding,WSDualHttpBinding和WSFederationHttpBinding。

服务接口的方法(操作)必须标记为attribute System.ServiceModel.TransactionFlowAttribute然后,使用某些属性参数并在设置TransactionScopeRequired属性参数时,System.ServiceModel.OperationBehaviorAttribute将在客户端和服务之间分配事务。另外,默认情况下,除非在运行时引发异常,否则该服务将被视为投票提交事务。要更改此行为,必须设置相应的TransactionAutoComplete属性参数value System.ServiceModel.OperationBehaviorAttribute

支持分布式事务的简单WCF服务的代码。
 [System.ServiceModel.ServiceContract] public interface IMyService { [System.ServiceModel.OperationContract] [System.ServiceModel.TransactionFlow(System.ServiceModel.TransactionFlowOption.Mandatory)] int DoSomething(string input); } public class MyService : IMyService { [System.ServiceModel.OperationBehavior(TransactionScopeRequired = true)] [System.ServiceModel.TransactionFlow(System.ServiceModel.TransactionFlowOption.Mandatory)] public int DoSomething(string input) { if (input == null) throw new System.ArgumentNullException(nameof(input)); return input.Length; } } 

, System.ServiceModel.TransactionFlow System.ServiceModel.OperationBehavior .

此服务的样本配置。
 <system.serviceModel> <services> <service name="WcfWithTransactionsExample.MyService" behaviorConfiguration="serviceBehavior"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="mainWsBinding" contract="WcfWithTransactionsExample.IMyService"/> <endpoint address="mex" contract="IMetadataExchange" binding="mexHttpBinding"/> </service> </services> <bindings> <wsHttpBinding> <binding name="mainWsBinding" maxReceivedMessageSize="209715200" maxBufferPoolSize="209715200" transactionFlow="true" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"> <security mode="None"/> <readerQuotas maxArrayLength="209715200" maxStringContentLength="209715200"/> </binding> </wsHttpBinding> </bindings> </system.serviceModel> 

, WSHttpBinding transactionFlow="true" .

TL; DR部分


分布式事务包括多个资源管理器,并且所有更改都必须提交或回滚。一些现代的DBMS实现了分布式事务,这些事务提供了用于连接多个数据库的便捷机制。软件(未在DBMS中实现)分布式事务可能包括运行不同操作系统的不同计算机上的资源管理器的不同组合,但是它们具有局限性,在依赖它们之前必须加以考虑。分布式事务的一种现代替代方法是消息传递解决方案。在.NET Core中,尚不支持分布式事务。

WCF是用于创建和访问.NET世界中的服务的标准且经过验证的工具之一,它支持多种类型的传输和加密。WCF与基于.NET的分布式事务非常友好System.Transactions为WCF设置分布式事务包括:用几个属性标记代码,并在服务和客户端配置文件中添加几个单词。并非所有WCF绑定都支持分布式事务。此外,很明显,WCF中的事务与不使用WCF时具有相同的限制。到目前为止,.NET Core平台仅允许您访问WCF上的服务,而不能创建它们。

结论婴儿床


这篇文章概述了.NET软件事务的基础。关于软件交易趋势的一些结论可以在所讨论主题的适用性和局限性部分中找到,并且总而言之,收集了该出版物的主要论文。我认为在将软件交易视为实现技术系统或刷新内存中相关信息的一种选择时,它们可以用作备忘单。

事务(主题区域,DBMS,软件)。领域需求有时以事务的形式来表述-从完整数据领域开始的操作,在完成(包括不成功)之后也应该进入完整数据领域(可能已经不同)。这些要求通常实现为系统事务。交易的经典示例是在两个帐户之间进行资金转移,这包括两个不可分割的操作-从一个帐户中提取资金并存入另一个帐户。除了由DBMS实现的众所周知的事务外,例如,.NET世界中还存在软件事务。资源管理器是软件组件,它们知道此类事务的存在并具有被包含在其中的能力,即提交或回滚所做的更改。资源管理器从事务管理器接收有关提交和回滚更改的指令,这是基础结构的基础System.Transactions

耐用且间歇性的资源管理器。长期资源管理器支持系统故障后的数据恢复。 .NET的DBMS驱动程序通常提供这种功能。间歇性资源管理器支持灾难恢复。程序性事务内存(一种在RAM中管理对象的方式)可以看作是善变的资源管理器的一个示例。

事务和.NET资源管理器。 .NET程序员使用软件事务并使用命名空间中的类型创建自己的资源管理器System.Transactions。该基础结构允许使用各种嵌套和隔离的事务(具有已知限制)。使用事务并不复杂,它包括将代码包装在using具有某些特征的块中。但是,以这种方式包含在事务中的资源管理器必须维护其所需的功能。在事务中使用异构资源管理器或以不同方式使用一个管理器可以自动将事务转换为分布式事务。

分布式事务(DBMS,软件)。分布式事务包含多个子系统,这些子系统中的更改必须同步,也就是说,它们要么一起提交,要么回滚。分布式事务是在某些现代DBMS中实现的。软件分布式事务(不是DBMS实施的事务)对交互过程和平台施加了其他限制。分布式事务逐渐过时,让位于基于消息传递服务的解决方案。要将普通事务转换为分布式事务,程序员无需执行任何操作:当运行时事务中包含具有某些特征的资源管理器时,该事务管理器将自动执行所需的任何操作。常规软件事务在.NET Core和.NET Standard中可用,而分布式事务不可用。

通过WCF进行分布式事务。WCF是用于创建和调用服务的标准.NET工具之一,它也支持标准化协议。换句话说,可以通过任何应用程序访问以特定方式配置的WCF服务,而不仅仅是.NET或Windows。要在WCF之上创建分布式事务,您需要使用其他属性标记组成服务的类型,并对服务和客户端配置文件进行最少的更改。您不能在.NET Core和.NET Standard中创建WCF服务,但可以创建WCF客户端。

在GitHub上检查System.Transactions的示例

参考文献


ACID ( «»)
( Microsoft)
( Microsoft)
( «»)
( «»)
( «»)


X/Open XA ( The Open Group)
Java Transaction API ( «»)
Cache ( InterSystems)

.NET


.NET ( Tech Blog Collection)
TransactionScope («»)
, ( Microsoft)
.NET Standard 2.0 ( .NET Standard GitHub)
( YarFullStack)


(«»)
(«»)
«» («»)
«» («»)
(«»)

社论

Source: https://habr.com/ru/post/zh-CN433136/


All Articles