Java理论和实践: 理解JTS平衡安全性和性能

欢迎进入Java社区论坛,与200万技术人员互动交流 >>进入

  J2EE 规范定义了六种事务模式: Required 、 RequiresNew 、 Mandatory 、 Supports 、 NotSupported 和 Never 。表 1 概述了每种模式的行为 ? 在现有事务中被调用和不在事务内调用时的行为 ? 并描述了每种模式受哪些类型的 EJB 组件支持。(一些容器可能允许您在选择事务模式时有更多的灵活性,但这种使用要依赖特定于容器的功能,因此不适合跨容器的情况)。

  表 1. 事务模式

事务模式Bean 类型在事务 T 内被调用时的行为在事务外被调用时的行为Required会话、实体、消息驱动在 T 中征用新建事务RequiresNew会话、实体新建事务新建事务Supports会话、消息驱动在 T 中征用不带事务运行Mandatory会话、实体在 T 中征用出错NotSupported会话、消息驱动不带事务运行不带事务运行Never会话、消息驱动出错不带事务运行

  在只使用容器管理的事务的应用程序中,只有组件调用事务模式为 Required 或 RequiresNew 的 EJB 方法时才启动事务。如果容器创建一个事务作为调用事务性方法的结果,当该方法完成时将关闭该事务。如果方法正常返回,容器将提交事务(除非应用程序已经要求回滚事务)。如果方法通过抛出一个异常退出,容器将回滚事务并传播该异常。如果在现有事务 T 中调用了一个方法,并且事务模式指定应该不带事务运行该方法或者在新事务中运行该方法,那么事务 T 将被暂挂,一直到方法完成,然后先前的事务 T 被恢复。

  选择一种事务模式

  那么我们应该为自己的 bean 方法选择哪种模式呢?对于会话 bean 和消息驱动 bean,您通常想使用 Required 来确保每个调用都被作为事务的一部分执行,但仍将允许方法作为一个更大的事务的组件。请小心使用 RequiresNew ;只有在确定自己的方法的行为应该与调用您的方法的行为分开提交时,才应该使用这种模式。 RequiresNew 一般情况下只和与系统中其它对象关系很少或没什么关系的对象(比如日志对象)一起使用。(把 RequiresNew 与日志对象一起使用比较有意义,因为您可能希望在不管外围事务是否提交的情况下提交日志消息。)

  RequiresNew 使用不当会导致与上面的描述相似的情况,其中,清单 1 中的代码在五个分开的事务而不是一个事务中执行,这样会使应用程序处于不一致状态。

  对于 CMP(容器管理的持久性,container-managed persistence)实体 bean,通常是希望使用 Required 。 Mandatory 也是一个合理的选项,特别是在最初开发时;这将会警告您实体 bean 方法在事务外被调用这种情况,这时可能会指出一个部署错误。您几乎从不希望把 RequiresNew 和 CMP 实体 bean 一起使用。 NotSupported 和 Never 旨在用于非事务性资源,比如 Java 事务 API(Java Transaction API,JTA)事务中无法征用的外部非事务性系统或事务性系统的适配器。

  如果 EJB 应用程序设计得当,应用上面的事务模式指导往往会自然地产生规则 4 建议的事务划分。原因是 J2EE 体系架构鼓励把应用程序分解为最小的方便处理的块,并且每个块都作为一个单独的请求被处理( 不管是以 HTTP 请求的形式还是作为在 JMS 队列中排队的消息的结果)。

  重温隔离

  在第 1 部分中,我们定义了 隔离(isolation)的意思是:一个事务的影响对与该事务并发执行的其它事务是不可见的;从事务的角度来看,好象事务是连续执行而非并行执行。尽管事务性资源管理器经常可以同时处理许多事务并提供隔离的假象,但有时隔离限制实际上要求把新事务延迟到现有事务完成后才开始。由于完成一个事务至少包括一个同步磁盘 I/O(写到事务日志),这就会把每秒的事务数限制到接近每秒的写磁盘次数,这对可伸缩性不利。

  实际上,通常是充分放松隔离需求以允许更多的事务并发执行并使系统响应能够得到改善,使可伸缩性变得更强。几乎所有的数据库都支持标准隔离级别:读未提交的(Read Uncommitted)、读已提交的(Read Committed)、可重复的读(Repeatable Read) 和可串行化的(Serializable)。

  不幸的是,为容器管理的事务管理隔离目前是在 J2EE 规范的范围之外。但是,许多 J2EE 容器,比如 IBM WebSphere 和 BEA WebLogic,将提供特定于容器的扩展,这些扩展允许您以每方法(per-method)为基础设置事务隔离级别,设置方法与在装配描述符中设置事务模式的方法相同。对于 bean 管理的事务,您可以通过 JDBC 或者其它资源管理器连接设置隔离级别。

  为阐明隔离级别之间的差异,我们首先把几个并发危险分类 ? 这几种危险是当没有适当地隔离时一个事务可能会干涉另一个事务的情况。下列的所有这些危险都与这种情况( 第二个事务已经启动后第一个事务变得对第二个事务 可见)的结果有关:

  脏读(Dirty Read):当一个事务的中间(未提交的)结果对另一个事务可见时就会发生这种情况。

  不可重复的读(Unrepeatable Read):当一个事务读取一个数据项,然后重新读取这个数据项并看到不同的值时就是发生了这种情况。

  虚读(Phantom Read):当一个事务执行返回多个行的查询,稍后再次执行同一个查询并看到第一次执行该查询没出现的额外行时就是发生了这种情况。

  四个标准隔离级别与这三个隔离危险相关,如表 2 所示。最低的隔离级别“读未提交的”并不能保护事务不被其它事务更改,但它的速度最快,因为它不需要争夺读锁。最高的隔离级别“可串行化的”与上面给出的隔离的定义相当;每个事务好象都与其它事务的影响完全隔离。

  表 2. 事务隔离级别

隔离级别脏读不可重复的读虚读读未提交的是是是读已提交的否是是可重复的读否否是可串行化的否否否

  对于大多数数据库,缺省的隔离级别为“读已提交的”,这是个很好的缺省选择,因为它阻止事务在事务中的任何给定的点看到应用程序数据的不一致视图。“读已提交的”是一个很不错的隔离级别,用于大多数典型的短事务,比如获取报表数据或获取要显示给用户的数据的时候(多半是作为 Web 请求的结果),也用于将新数据插入到数据库的情况。

  当您需要所有事务间有较高级别的一致性时,使用较高的隔离级别“可重复的读”和“可串行化的”比较合适,比如在清单 1 示例中,您希望从检查余额以确保有足够的资金到您实际取钱期间账户余额一直保持不变;这就要求至少要用“可重复的读”隔离级别。在数据一致性绝对重要的情况下,比如审核记帐数据库以确保一个帐户的所有借方金额和贷方金额的总数等于它目前的余额时,可能还需要防止创建新行。这种情况下就需要使用“可串行化的”隔离级别。

  最低的隔离级别“读未提交的”很少使用。它适用于您只需要获得近似值,否则查询将导致您不希望的性能开销这种情况。当您想要估计一个变化很快的数量,如定单数或者今天所下定单的总金额(以美元为单位)时一般使用““读未提交的”。

  因为隔离和可伸缩性之间实际是一种此消彼长的关系,所以您在为事务选择隔离级别时应该小心行事。选择太低的级别对数据比较危险。选择太高的级别可能对性能不利,尽管负载比较轻时可能不会这样。一般来说,数据一致性问题比性能问题更严重。如果拿不准,应该以小心为主,选择一个较高的隔离级别。这就引出了规则 5:

  规则 5:使用保证数据安全 style=”COLOR: #000000″ href=”http://safe.it168.com/” target=_blank>安全的最低隔离级别,但如果拿不准,请使用“可串行化的”。

  即使您打算刚开始时以小心为主并希望结果性能可以接受 ?(被称为“拒绝和祈祷(denial and prayer)”的性能管理技术 ? 很可能是最常用的性能策略,尽管大多数开发者都不承认这一点),在开发组件时考虑隔离需求也是有利的。您应该努力编写能够容忍级别较低但实用的隔离级别的事务,这样,当稍后性能成为问题时,自己就不会陷入困境。因为您需要知道方法正在做什么以及这个方法中隐藏了什么一致性假设来正确设置隔离级别,那么在开发期间仔细说明并发需求和假设,以便在装配应用程序时帮助作出正确的决定也不失为一个好主意。

  结束语

  本文中提供的许多指导可能看起来有点互相矛盾,因为象事务划分和隔离这种问题本来就是此消彼长的。我们正在努力平衡安全性(如果我们不关心安全性,那就压根不必用事务了)和我们用来提供安全限度的工具的性能开销。正确的平衡要依赖许多因素,包括与系统故障或当机时间相关的代价或损害以及组织的风险承受能力。

[1][2]

接受失败等于回归真实的自我,

Java理论和实践: 理解JTS平衡安全性和性能

相关文章:

你感兴趣的文章:

标签云: