权衡ApacheGeronimoEJB事务选项,第3部分:综合所有事务

Jonathan Sagorin 在 由三部分组成的系列文章 的最后一部分中对 Enterprise Java™Beans(EJB)事务进行了全面的揭示。探索 Apache Geronimo 应用服务器上与容器管理事务和 bean 管理事务都相关的难题和附加实现与配置选项。

简介

在本系列的 第 1 部分 和 第 2 部分 中,您简单了解了 bean 管理的和容器管理的 EJB 事务以及如何在 Geronimo 应用服务器上实现它们。那么接下来呢?在使用 EJB 事务时,您应考虑哪些其他事务设置是可用的,以及需要考虑的其他事项?

本文首先概述第 1 和第 2 部分中的事务选择:容器管理或 bean 管理的事务。接着将了解并发控制策略和方法,确保事务执行时没有数据损失。您还将了解隔离级别(如何控制事务与其他事务的隔离)并了解如何设置事务超时。最后,您将了解赞成和反对使用分布式事务的理由。

EJB 事务:我的选择是什么?

在实现 EJB 事务时,有两种选择:容器管理或 bean 管理的事务。

使用容器管理的事务时,将在部署描述符中指定事务行为。EJB 容器负责控制事务边界。您为整个 enterprise bean、bean 上的个别方法或两者指定事务属性。事务属性的选择有:

Required

RequiresNew

Supports

Mandatory

NotSupported

Never

使用 bean 管理的事务时,您编程控制事务边界并决定事务开始、提交和回滚的时机。在 bean 管理的事务中,可以在 Java Transaction API(JTA)或 Java Database Connectivity(JDBC)事务实现之间进行选择。JTA 使用 javax.transaction.UserTransaction 接口控制事务,而 JDBC 事务则直接经 java.sql.Connection 接口执行操作来控制事务行为。

如果使用会话或消息驱动 bean(MDB),那么可以实现 bean 管理或容器管理事务。然而,实体 bean 只可以使用容器管理事务。

表 1 按每个 enterprise bean 实现的事务类型总结了这些选择。

表 1. enteprise bean 的事务类型选择

事务类型 会话 bean 实体 bean 消息驱动 bean bean 管理 x x 事务管理 x x x

如果不确定您的 bean 使用哪种事务类型,Sun Microsystems 建议对 enterprise bean 使用具有 required 属性的容器管理的事务。

对于开发人员,使用容器管理的事务会比较简单并且需更比较少的工作量。在 bean 方法中不要求事务逻辑。在 enterprise bean 的方法级别上划分事务边界。bean 方法必须运行在事务上下文中或不在其中运行。

如果要求对事务边界进行更严格的控制,那么使用 bean 管理的事务。如果期望在 enterprise bean 中拥有长期过程,那么使用 bean 管理的事务。对于本文的目的,希望事务运行尽可能短的时间。如果使用容器管理事务,划分边界的粒度不够细,它们处于 bean 方法级别。

通过使用 bean 管理的事务,可以限制事务的持续时间为短暂的。可以在事务中隔离数据库操作并允许长期过程在事务作用域之外运行。这将确保不会阻塞访问同一数据的其他任何事务。

并发控制策略

使用 EJB 实现并发控制有两种策略:可以遵循消极(pessimistic)或积极(optimistic)锁定策略。

使用消极锁定 时,在修改数据所需的事务持续时间内获取锁,从而阻塞其他任何人修改同一数据。如果系统中有其他人可能试图修改正在处理的数据,那么这是使用该策略的好机会。该策略提供对数据的可靠访问,但是不适用于较小规模的系统,因为当要求系统规模和更多锁时,性能将会降低。

积极锁定 不会在事务期间持有锁。您采取积极的态度并假设在您使用数据时数据不会被其他事务修改。当发生数据冲突时,您可以处理异常情况,但假设这是很少发生的。当要求更新数据时,实现策略来检查数据在最近被读取之后和在被修改之前这段时间里没有发生改变。如果数据没有改变,就执行更新。该策略适用于大规模系统。缺点是您必须实现检测和处理数据冲突的代码。

下一节将讨论改变事务的隔离级别。这是消极锁定的一个例子,其中通过改变事务的隔离级别来控制消极锁定的程度和效力。

隔离

事务的 ACID 属性之一是隔离性。隔离允许事务行为(无论是读还是写数据)独立或隔离于其他并发运行的事务。通过控制隔离,每个事务在其行动时间里都像是修改数据库的惟一事务。一个事务与其他事务隔离的程度称为隔离级别。

锁机制和同步用来控制隔离级别。随着隔离级别增加,需要更多的锁和同步。由于锁控制数据资源,其他尝试执行任何数据操作的事务必须等待,直到锁被释放。因此,增加隔离级别将以性能为代价。相反,随者隔离级别降低,因事务耗费较少的时间来等待锁被释放,将提高性能。

数据一致性

对事务设置隔离级别来处理下列数据一致性问题:

脏数据读(Dirty read)

不可重复读(Unrepeatable read)

影像读(Phantom read)

脏数据读

当从数据库读取未经提交的数据时,发生脏数据读。读取的数据没有与数据库里的真实数据同步。

考虑下面的场景,其中两个事务在读取和更新数据库的 String 字段 X。String X 的初始值是 foo:

事务 1 读取 String X 的值 foo。

事务 1 将 String X 的当前值与 bar 连接并将其保存到数据库中。

X 的新值是 foobar。事务 1 还没有发出提交语句。

事务 2 读取 String X 的值,即 foobar。

事务 1 中止。

事务 2 将 String X 与 bar 连接并将其保存至数据库。

X 的新值是 foobarbar,这时其正确的值应该是 foobar。

事务 2 对 String X 的值执行了脏数据读。

这里的问题是一个事务可以改变值,而另一个事务可能在初始修改被提交前读取其值。数据是脏的,并不能表示数据的真实状态。

不可重复读

如果应用程序从数据库读取数据,然后又重读已经被修改的数据(可能以后在同一事务中),那么会发生不可重复读。考虑下面的场景,其中两个应用程序在读取和更新同一数据:

应用程序 1 读取 String X 的值 foo。

应用程序 2 更新 String X 的值为 foobar。

应用程序 1 重读 String 的值并发现其已改为 foobar。

因此在读取之间,数据的值已改变并变得不一致。

影像读

影像读 与不可重复读类似。但是,影像读会将新数据插入到数据库中。应用程序从数据库中读取数据集,然后确定在其重读同一数据集时,附加的数据已经被添加。考虑下面的场景,其中两个应用程序在读取和更新数据库的同一数据:

应用程序 1 搜索符合某个条件的数据并返回五行的数据集。

应用程序 2 将五个满足应用程序 1 的搜索条件的附加行添加到数据库。

在应用程序 1 基于其初始条件重读数据库(且期望得到五行)时,将返回 10 行。

同样,在读取之间数据变得不一致。

选择隔离级别

隔离的四个级别列在下面,按最低(最弱)隔离级别到最高(最强)级别的顺序排列。记住当提高隔离级别时,应用程序的性能将降低。

Read uncommitted - 该选项仅适用于具有非共享数据的非任务关键型系统(这在应用程序中是很少见的情况)。性能处于最佳状态,但是将牺牲并发控制。如果确定没有其他并发事务,请使用该选项。使用该选项,以上所列的数据问题都无法解决。

Read committed - 这是大多数数据库的默认隔离级别,也是 Apache Geronimo 默认的。只能读取提交的数据,因此该选项解决了脏数据读问题。由于要求对数据库使用附加锁,因此性能将会慢一些。

Repeatable read - 通过使用该隔离级别,解决脏数据读和未提交读问题。可以保证读取的任何行可以在以后被重读而其值将不会改变。

Serializable - 这是最严格的隔离级别,解决了所有三个数据问题。在希望事务以真正隔离的方式运行并完全与其他事务独立时,请使用该级别。这将保证数据一致性。对任务关键型系统使用它来保证真正地隔离事务行为。但是注意,该隔离级别是以性能为代价。

表 2 概述隔离级别选择并显示每个级别如何解决 早先 列出的三个数据问题。

表 2. 使用隔离级别解决数据问题

数据解决方案 Read uncommitted Read committed Repeatable read Serializable 解决脏数据读 x x x 解决不可重复读 x x 解决影像读 x

在 bean 管理的事务中的隔离级别

通过底层的数据库资源管理器指定隔离级别。使用 bean 管理的事务时,可以编程访问底层连接。由于可以访问 java.sql.Connection 接口,可以使用方法 setTransactionIsolation(int level) 改变连接的隔离级别。

使用下面这些常量来设置恰当的隔离级别:

Connection.TRANSACTION_READ_UNCOMMITTED

Connection.TRANSACTION_READ_COMMITTED

Connection.TRANSACTION_REPEATABLE_READ

Connection.TRANSACTION_SERIALIZABLE

其他让人感兴趣的方法是:

Connection.getTransactionIsolation()

DatabaseMetaData.supportsTransactionIsolationLevel(int)

(参考 参考资料 部分中的 Sun JavaDoc API 来获得有关这些方法的更多信息。)

注意:通过修改隔离级别,要求数据库资源管理器改变该资源的隔离性。这不是数据库供应商必须提供的必要特性。实际上,很多数据库供应商不会允许这么做。在改变隔离级别时一定要小心。查看您的数据库资源管理器文档来查明支持的隔离级别。

另外,应该在开始事务之前设置事务隔离级别。决不能在事务的过程中切换隔离级别。大多数资源管理器还要求对事务中的所有参与者使用同一隔离级别。

在容器管理的事务中的隔离级别

在使用容器事务时,没有办法在部署描述符中指定隔离级别。默认情况下,Geronimo 对 EJB 容器使用 Read Committed 隔离级别。如果需要对隔离级别更细的控制,那么考虑在 bean 管理的事务中使用 JDBC 事务。

事务超时

在 bean 管理的事务中使用 JTA 事务时,可以使用 javax.transaction.UserTransaction 接口的 setTransactionTimeout 方法。这将设置在事务中止之前将运行的最大时间(秒)。

分布式事务

在单个事务中的多个参与者物理分布于网络中时,事务称为分布式事务。分布式事务允许不同类型的资源参与到事务中。分布式事务的例子是:

单个会话 bean 开始事务并更新数据库 A。调用运行在同一个应用服务器上的另一个会话 bean 来更新数据库 B。第一个会话 bean 提交事务。两个数据库更新发生在同一个事务中。

单个会话 bean 开始事务并更新数据库 A。调用运行在不同的 应用服务器上的另一个会话 bean 来更新数据库 B。每个应用服务器的事务管理器将确保两个数据库在同一事务中更新。

单个会话 bean 开始事务并更新数据库 A,接着是 Java Message Service(JMS)操作。两个工作单元都是同一事务的一部分。如果 JMS 操作失败,事务将不会更新数据库。

几个事务管理器必须共同工作来执行分布式事务。通常指定一个单独的事务管理器(称为事务协调器或分布式事务管理器)来协调其他事务管理器。

事务管理器进而与资源管理器进行协调来对资源(可能是数据库或消息服务器)执行必需的提交或回滚。大多数数据库的事务管理器和资源管理器紧密地耦合在一起。

两段式提交

通过使用某种协议进行通信来完成分布式事务,被称为两段式提交。从名字上看,您可能已经知道有两个阶段:

第一个阶段,即预提交:

事务协调器给每个事务管理器发送准备操作的信号。

事务管理器将操作(通常是数据更新)步骤(或细节)写入事务日志。如果失败,事务管理器使用这些步骤重复操作。

事务管理器本地创建事务并通知资源管理器对资源(例如,数据库或消息服务器)执行操作。

资源管理器执行操作并向事务管理器报告成功(准备提交信号)或失败(准备回滚)。

资源管理器等待事务管理器进一步的指令。

事务管理器向事务协调器报告成功或失败。

第二阶段,即提交阶段:在第二阶段中,第一阶段的结果将传送给所有事务管理器。如果任何事务管理器报告失败,所有的事务参与者都必须回滚。

事务协调器让所有事务管理器提交(或回滚)。

所有事务管理器将提交或回滚信息传递给其资源管理器。

资源管理器将成功或失败提示返回给事务管理器。

事务管理器向事务协调器报告成功或失败。

我们可以交谈吗?

分布式事务的最大挑战是对事务管理器之间的共用通信协议取得一致。用于两段式提交的标准化协议称为 XA 协议,但是并非所有的供应商都支持该标准。XA 协议定义了事务管理器和资源管理器之间的接口。

Geronimo 的事务管理器是 Java Open Transaction Manager(JOTM),一个开放源码的事务管理器。它实现 XA 协议并编译成 JTA。记住,JTA 是本系列文章中用来与事务管理器进行通信的接口。可以在 bean 管理的事务中使用其来指定何时启动、提交或回滚事务。

只要所有的事务参与者都对通信协议取得一致,它们就可以参与同一分布式事务。

它不是完美的

注意,使用分布式事务比使用本地事务要慢。所有事务参与者都需要更多的系统资源。事务协调器和所有事务参与者之间的网络交流都将影响系统响应时间。分布式事务由于涉及的事务管理器和资源管理器的数量,还将花费更多时间。

结束语

在这个介绍性的 关于 EJB 事务的系列 的最后一部分中,您已看到对选择的总结,并讨论了在 Geronimo 中使用 EJB 事务的一些附加配置选项及选择。

在认识你之后,我才发现自己可以这样情愿的付出……

权衡ApacheGeronimoEJB事务选项,第3部分:综合所有事务

相关文章:

你感兴趣的文章:

标签云: