jts-1.14_了解JTS-交易简介

如果您查看有关J2EE的介绍性文章或书籍,则只会发现一小部分专门用于Java事务处理服务(JTS)或Java事务处理API(JTA)的资料。 这不是因为JTS是J2EE的不重要或可选部分-恰恰相反。 JTS的压力不如EJB技术,因为它为应用程序提供的服务在很大程度上是透明的-许多开发人员甚至都不知道事务在应用程序中的开始和结束位置。 从某种意义上说,JTS的默默无闻是由于其自身的成功:因为它如此有效地隐藏了事务管理的细节,所以我们对此并没有太多听到或说太多。 但是,您可能想了解它在幕后的作用。

毫不夸张地说,没有事务,编写可靠的分布式应用程序几乎是不可能的。 事务使我们能够以受控方式修改应用程序的持久状态,从而使我们的应用程序对各种系统故障(包括系统崩溃,网络故障,电源故障甚至自然灾害)具有鲁棒性。 事务是构建容错,高度可靠和高度可用的应用程序所需的基本构建块之一。

交易动机

假设您要将资金从一个帐户转移到另一个帐户。 每个帐户余额由数据库表中的一行表示。 如果要将资金从帐户A转移到帐户B,则可能会执行一些如下所示SQL代码:

SELECT accountBalance INTO aBalance 
    FROM Accounts WHERE accountId=aId;
IF (aBalance >= transferAmount) THEN 
    UPDATE Accounts 
        SET accountBalance = accountBalance - transferAmount
        WHERE accountId = aId;
    UPDATE Accounts 
        SET accountBalance = accountBalance + transferAmount
        WHERE accountId = bId;
    INSERT INTO AccountJournal (accountId, amount)
        VALUES (aId, -transferAmount);
    INSERT INTO AccountJournal (accountId, amount)
        VALUES (bId, transferAmount);
ELSE
    FAIL "Insufficient funds in account";
END IF

到目前为止,这段代码看起来相当简单。 如果A手头有足够的资金,则将从一个帐户中减去钱,然后将其添加到另一个帐户中。 但是在电源故障或系统崩溃的情况下会发生什么? 代表帐户A和帐户B的行不太可能存储在同一磁盘块中,这意味着完成传输将需要多个磁盘IO。 如果在第一个写入之后但第二个写入之前系统出现故障怎么办? 然后,钱可能已经离开了A的帐户,但没有显示在B的帐户中(A和B都不会这样),或者也许钱会出现在B的帐户中,但不会从A的帐户中扣除(银行不会这样) 。)或如果帐户已正确更新但帐户日记帐未正确怎么办? 这样,A和B每月银行对帐单上的活动将与他们的帐户余额不一致。

不仅不可能同时将多个数据块写入磁盘,而且当部分数据发生更改时将每个数据块写入磁盘都会对系统性能造成不利影响。 将磁盘写入延迟到更合适的时间可以大大提高应用程序的吞吐量,但是需要以不损害数据完整性的方式来完成。

即使没有系统故障,在上面的代码中还有另一个值得讨论的风险-并发性。 如果A的帐户中有$ 100,但又在同一时间向两个不同的帐户发起了两次$ 100的转帐,该怎么办? 如果我们的时机不佳,没有适当的锁定机制,两次传输都可能成功,从而使A的余额保持负数。

这些场景很合理,可以合理地预期企业数据系统可以应对它们。 我们期望银行在发生火灾,洪水,电源故障,磁盘故障和系统故障时能够正确维护帐户记录。 容错可以通过冗余(冗余磁盘,计算机甚至数据中心)来提供,但是正是这些事务使构建容错软件应用程序变得切实可行。 事务提供了一个框架,用于在面对系统或组件故障时强制执行数据一致性和完整性。

什么是交易?

那么什么是交易? 在定义此术语之前,首先我们将定义应用程序状态的概念。 应用程序的状态包含影响应用程序操作的所有内存中数据和磁盘上数据项-应用程序“知道”的所有内容。 应用程序状态可以存储在内存,文件或数据库中。 如果发生系统故障(例如,如果应用程序,网络或计算机系统崩溃),我们希望确保在重新启动系统后可以恢复应用程序的状态。

现在,我们可以将事务定义为对应用程序状态的相关操作集合,该事务具有原子性 , 一致性 , 隔离 性和持久性 。 这些属性统称为ACID属性。

原子性意味着要么将所有事务的操作都应用到应用程序状态,要么不应用任何事务。 交易是不可分割的工作单元。

一致性意味着事务表示应用程序状态的正确转换-事务不会违React用程序中隐含的任何完整性约束。 实际上,一致性的概念是特定于应用程序的。 例如,在会计应用程序中,一致性应包括所有资产帐户的总和等于所有负债帐户的总和的不变性。 当我们在本系列的第3部分中讨论事务划分时,我们将回到该要求。

隔离意味着一个事务的影响不会影响同时执行的其他事务; 从事务的角度看,似乎事务是顺序执行的,而不是并行执行的。 在数据库系统中,通常使用锁定机制来实现隔离。 对于某些事务,有时会放宽隔离要求,以产生更好的应用程序性能。

持久性意味着一旦事务成功完成,对应用程序状态的更改将在失败后继续存在。

“生存失败”是什么意思? 什么是可幸存的失败? 这取决于系统,设计良好的系统将明确标识可以从中恢复的故障。 在我的台式工作站上运行的事务性数据库对于系统崩溃和电源故障具有鲁棒性,但对我的办公楼烧毁却没有作用。 一家银行不仅可能在其数据中心中具有冗余磁盘,网络和系统,而且在通过冗余通信链路连接的不同城市中可能还具有冗余数据中心,以允许从自然灾害等严重故障中恢复过来。 军用数据系统可能对容错性的要求更高。

交易剖析

典型的事务有多个参与者-应用程序,事务处理监视器(TPM)和一个或多个资源管理器(RM)。 RM存储应用程序状态,并且通常是数据库,但也可以是消息队列服务器(在J2EE应用程序中,它们将是JMS提供程序)或其他事务资源。 TPM协调RM的活动,以确保交易的全有或全无。

当应用程序要求容器或事务监视器启动新事务时,事务开始。 由于应用程序访问各种各样的RM,他们入伍交易。 RM必须将对应用程序状态的任何更改与请求更改的事务相关联。

当发生以下两种情况之一时,事务结束:事务由应用程序提交 ,或者事务被应用程序回滚或者由于RM之一失败而回滚 。 如果事务成功提交,则与该事务关联的更改将被写入持久性存储中,并使新事务可见。 如果回滚,则该事务所做的所有更改都将被丢弃; 好像交易从未发生过。

事务日志-持久性的关键

事务RM通过在单个事务日志中汇总多个事务的结果来实现具有可接受性能的持久性。 事务日志存储为顺序磁盘文件(或有时存储在原始分区中),除非回滚或恢复,否则通常仅写入而不从中读取。 在我们的银行帐户示例中,与帐户A和B相关联的余额将在内存中更新,并将新旧余额写入事务日志。 将更新记录写入事务日志需要将较少的总数据写入磁盘(只需写入已更改的数据,而不是整个磁盘块)和较少的磁盘查找(因为所有更改都可以包含在顺序中此外,可以将与多个并发事务相关联的更改合并到对事务日志的一次写入中,这意味着我们可以在每个磁盘写入中处理多个事务,而不是每个事务需要多个磁盘写入。 稍后,RM将更新与更改后的数据相对应的实际磁盘块。

重新启动后恢复

如果系统发生故障,则重启后要做的第一件事就是重新应用日志中存在但其数据块尚未更新的所有已提交事务的影响。 这样,日志可以保证所有故障的持久性,还使我们能够减少执行的磁盘IO操作的数量,或者至少将它们推迟到对系统性能的影响较小的时间。

两阶段提交

许多事务只涉及一个RM,通常是一个数据库。 在这种情况下,RM通常会执行大部分工作来提交或回滚事务。 (几乎所有的事务RM都内置有自己的事务管理器,该事务管理器可以处理本地事务 -仅涉及该RM的事务。)但是,如果事务涉及两个或更多RM,则可能是两个单独的数据库,或者是一个数据库和一个JMS队列或两个单独的JMS提供程序-我们要确保“全有或全无”语义不仅适用于RM,而且适用于事务中的所有RM。 在这种情况下,TPM将协调两个阶段的提交 。 在两阶段提交中,TPM首先向每个RM发送“准备”消息,询问它是否准备就绪并且能够提交事务; 如果收到所有RM的肯定答复,它将在其自己的事务日志中将事务标记为已提交,然后指示所有RM提交事务。 如果RM失败,则重启后它将询问TPM有关失败时任何未决事务的状态,然后提交或回滚。

两阶段提交的社会比喻是婚礼-牧师或法官首先要求每一方“您以这个男人/女人作为您的丈夫/妻子吗?” 如果双方都同意,则双方都宣布已婚; 否则,他们都未婚。 不论谁先说“我愿意”,任何一方在任何情况下都不会结婚,而另一方则不会。

事务是一种异常处理机制

您可能已经观察到,事务为应用程序数据提供了许多与同步块为内存中数据提供的功能相同的功能-保证原子性,更改的可见性和明显的顺序。 但是,虽然同步主要是一种并发控制机制,但是事务主要是一种异常处理机制。 在磁盘不发生故障,系统和软件不崩溃,电源100%可靠的世界中,我们不需要事务。 交易在合同法在社会中扮演的企业应用程序中扮演着重要的角色-它们指定了如果一方不履行其合同义务时如何取消承诺。 总体而言,当我们签订合同时,我们通常希望它是多余的。

与更简单的Java程序类似,事务在应用程序级别提供了与catchfinally在方法级别执行的块相同的某些优点; 它们使我们能够执行可靠的错误恢复,而无需编写大量错误恢复代码。 考虑这种方法,它将一个文件复制到另一个文件:

public boolean copyFile(String inFile, String outFile) {
  InputStream is = null;
  OutputStream os = null;
  byte[] buffer;
  boolean success = true;

  try {
    is = new FileInputStream(inFile);
    os = new FileOutputStream(outFile);
    buffer = new byte[is.available()];
    is.read(buffer);
    os.write(buffer);
  }
  catch {IOException e) {
    success = false;
  }
  catch (OutOfMemoryError e) {
    success = false;
  }
  finally {
    if (is != null)
      is.close();
    if (os != null)
      os.close();
  }

  return success;
}

忽略为整个文件分配单个缓冲区的事实是一个坏主意,这种方法可能会出错? 很多东西。 输入文件可能不存在,或者该用户可能没有读取权限。 该用户可能没有写输出文件的权限,或者可能已被另一个用户锁定。 可能没有足够的磁盘空间来完成文件写操作,或者如果没有足够的可用内存,分配缓冲区可能会失败。 幸运的是,所有这些都由finally子句处理,该子句释放了copyFile()使用的所有资源。

如果您是在过去的C天里编写这种方法的,那么对于每个操作(打开输入,打开输出,malloc,读取,写入),您都必须测试返回状态,如果操作失败,请撤消之前所有成功的操作操作并返回适当的状态码。 该代码将大很多,因此由于存在所有错误处理代码,因此更难阅读。 通过失败释放资源,两次释放资源或释放尚未分配的资源,也很容易在错误处理代码中出错(这也是最难测试的部分) 。 而且,如果采用一种更为实质性的方法,它可能会涉及比两个文件和一个缓冲区更多的资源,那么它将变得更加复杂。 在所有错误恢复代码中很难找到实际的程序逻辑。

现在想象一下您正在执行一个复杂的操作,该操作涉及在多个数据库中插入或更新多行,并且其中一项操作违反了完整性约束并且失败了。 如果要管理自己的错误恢复,则必须跟踪已经执行的操作以及在后续操作失败后如何撤消每个操作。 如果工作单元分布在多个方法或组件上,将变得更加困难。 使用事务来构造应用程序使您可以将所有簿记工作委托给数据库-只需说一下ROLLBACK,就可以撤消自事务开始以来所做的一切。

结论

通过使用事务来构造应用程序,我们定义了一组正确的应用程序状态转换,并确保即使在系统或组件出现故障之后,应用程序也始终处于正确的状态。 事务使我们能够将异常处理和恢复的许多元素委派给TPM和RM,从而简化了代码并使我们有更多的时间来思考应用程序逻辑。

在本系列的第2部分中,我们将探讨这对J2EE应用程序意味着什么-J2EE如何使我们能够向J2EE组件(EJB组件,Servlet和JSP页面)赋予事务性语义。 它如何使资源征用对应用程序完全透明,甚至对于Bean管理的事务也是如此; 以及单个事务如何透明地遵循从一个EJB组件到另一个EJB或从servlet到EJB组件的控制流,甚至跨多个系统。

即使J2EE相对透明地提供对象事务服务,应用程序设计人员仍必须仔细考虑在何处划分事务,以及我们将如何在应用程序中使用事务资源-错误的事务划分可能导致应用程序处于不一致状态,交易资源使用不当会导致严重的性能问题。 我们将处理这些问题,并在本系列的第3部分中提供有关如何组织交易的一些建议。


翻译自: https://www.ibm.com/developerworks/java/library/j-jtp0305/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值