事务策略之高并发策略.doc_第1页
事务策略之高并发策略.doc_第2页
事务策略之高并发策略.doc_第3页
事务策略之高并发策略.doc_第4页
事务策略之高并发策略.doc_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

我在本 系列 的前几篇文章中所介绍的 API 层 和 客户端编排策略 事务策略是应用于大多数标准业务应用程序的核心策略。它们简单、可靠、相对易于实现,并且提供了最高水平的数据完整性和一致性。但有时,您可能需要减小事务的作用域以获取吞吐量、改善性能并提高数据库的并发性。您如何才能实现这些目的,同时仍然维持高水平的数据完整性和一致性呢?答案是使用 High Concurrency 事务策略。High Concurrency 策略源自 API 层 策略。API 层策略虽然非常坚固和可靠,但它存在一些缺点。始终在调用栈的最高层(API 层)启动事务有时会效率低下,特别是对于具有高用户吞吐量和高数据库并发性需求的应用程序。限制特定的业务需求,长时间占用事务和长时间锁定都会消耗过多资源。关于本系列事务可以改善数据的质量、完整性和一致性,并使您的应用程序更加健壮。在 Java 应用程序中实现成功的事务处理并非易事,它涉及到设计和编码。在这个 系列文章 中,Mark Richards 将指导您为从简单应用程序到高性能事务处理等各种用例设计有效的事务策略。与 API 层策略类似,High Concurrency 策略释放了客户机层的任何事务责任。但是,这还意味着,您只能通过客户机层调用一次任何特定的逻辑工作单元(LUW)。High Concurrency 策略旨在减小事务的总体作用域,以便资源锁定的时间更短,从而增加应用程序的吞吐量、并发性以及性能。通过使用此策略所获取的好处在一定程度上将由您所使用的数据库以及它所采用的配置决定。一些数据库(比如说使用 InnoDB 引擎的 Oracle 和 MySQL)不会保留读取锁,而其他数据库(比如没有 Snapshot Isolation Level 的 SQL Server)则与之相反。保留的锁越多,无论它们是共享还是专用的,它们对数据库(以及应用程序)的并发性、性能和吞吐量的影响就越大。但是,获取并在数据库中保留锁仅仅是高并发性任务的一个部分。并发性和吞吐量还与您释放锁的时间有关。无论您使用何种数据库,不必要地长时间占用事务将更长地保留共享和专用锁。在高并发性下,这可能会造成数据库将锁级别从低级锁提高到页面级锁,并且在一些极端情况下,从页面级锁切换到表级锁。在多数情况下,您无法控制数据引擎用于选择何时升级锁级别的启发方法。一些数据库(比如 SQL Server)允许您禁用页面级锁,以期它不会从行级锁切换到表级锁。有时,这种赌博有用,但大多数情况下,您都不会实现预期中的并发性改善。底线是,在高数据库并发性的场景中,数据库锁定(共享或专用)的时间越长,则越有可能出现以下问题: 数据库连接耗尽,从而造成应用程序处于等待状态 由共享和专用锁造成的死锁,从而造成性能较差以及事务失败 从页面级锁升级到表级锁换句话说,应用程序在数据库中所处的时间越长,应用程序能处理的并发性就越低。我所列出的任何问题都会造成您的应用程序运行缓慢,并且将直接减少总体吞吐量和降低性能 以及应用程序处理大型并发性用户负载的能力。折衷High Concurrency 策略解决了高并发性需求,因为它能将事务在体系结构中的作用域尽可能减小。其结果是,事务会比在 API 层事务策略中更快地完成(提交或回滚)。但是,就像您从 Vasa 中学到的(见 参考资料),您不能同时拥有它们。生活中充满了折衷,事务处理也不例外。您不能期望提供与 API 层策略同样可靠的事务处理,同时提供最大的用户并发性和最高的吞吐量。 因此,您在使用 High Concurrency 事务策略时放弃了什么呢?根据您的应用程序的设计,您可能需要在事务作用域外部执行读取操作,即使读取操作用于更新目的。“等一等!”您说:“您不能这样做 您可能会更新在最后一次读取之后发生了变化的数据!”这是合理的担忧,并且也是需要开始考虑折衷的地方。通过此策略,由于您未对数据保持读取锁,因此在执行更新操作时遇到失效数据异常的机率会增加。但是,与 Vasa 的情况一样,所有这些都可以归结为一个问题,即哪个特性更加重要:可靠、坚固的事务策略(如 API 层策略),还是高用户并发性和吞吐量。在高并发性情形中,同时实现两者是极为困难的。如果您尝试这样做,则可能会适得其反。第二个折衷之处是事务可靠性的总体缺乏。此策略难以实现,并且需要更长的时间进行开发和测试,并且比 API 层或 Client Orchestration 策略更易于出错。考虑到这些折衷,您首先应该分析当前的情形以确定使用此策略是否是正确的方法。由于 High Concurrency 策略派生自 API 层策略,因此一种比较好的方法是先使用 API 层策略,并使用较高的用户负载对应用程序执行负载测试(比您预期的峰值负载更高)。如果您发现吞吐量较低、性能较第、等待次数非常多,或者甚至出现死锁,则要准备迁移到 High Concurrency 策略。在本文的其余部分,我将向您介绍 High Concurrency 事务策略的其他一些特性,以及实现它的两种方法。 基本结构和特性图 1 通过我在 事务策略 系列中所使用的逻辑应用程序栈展示了 High Concurrency 事务策略。包含事务逻辑的类显示为红色阴影。 图 1. 体系结构层和事务逻辑一些 API 层策略的特性和规则是有效的 但并非所有。注意,图 1 中的客户机层没有事务逻辑,这意味着任何类型的客户机都可以用于此事务策略,包括基于 Web 的客户机、桌面、Web 服务和 Java Message Service (JMS)。并且事务策略遍布于客户机下面的层中,但这不是绝对的。一些事务可能在 API 层中开始,一些在业务层中开始,还有一些甚至在 DAO 层中开始。这种一致性的缺乏是造成策略难以实现、维护和治理的原因之一。在大多数情况下,您会发现您需要使用 Programmatic Transaction 模型 来减小事务作用域,但有时您仍然会使用 Declarative Transaction 模型。但是,您通常不能在相同的应用程序中混用 Programmatic 和 Declarative Transaction 模型。在使用这种事务策略时,不应该坚持使用这种 Programmatic Transaction 模型,这样您就不会遇到各种问题。但是,如果您发现自己可以在此策略中使用 Declarative Transaction 模型,那么您应该在使用 REQUIRED 事务属性开始事务的层中标记所有公共写方法(插入、更新和删除)。此属性表示需要一个事务,并且如果事务不存在,则由方法启动。与其他事务策略一样,无论您选择开始事务的组件或层是什么,启动事务的方法都被认为是事务拥有者。只要可能,事务拥有者应该是对事务执行提交和回滚的唯一方法。事务策略实现您可以使用两个主要技巧来实现 High Concurrency 事务策略。先读取(read-first)技巧涉及在尽可能高的应用层(通常为 API 层)对事务作用域范围外的读取操作进行分组。低级(lower-level)技巧涉及在体系结构中尽可能低的层启动事务,同时仍然能够更新操作的原子性和隔离。先读取技巧先读取技巧涉及重构(或编写)应用程序逻辑和工作流,以便所有的处理和读取操作在事务作用域的外部首先发生。这种方法消除了不必要的共享或读取锁,但是如果数据在您能够提交工作之前更新或提交,则可能会引入失效数据异常。为了应对可能的这种情况,如果在此事务策略中使用对象关系映射(ORM)框架,则应确保使用了版本验证功能。为了演示这种先读取技巧,我们从一些实现 API 层事务策略的代码入手。在清单 1 中,事务在 API 层中开始,并且包围了整个工作单元,包括所有的读取、处理和更新操作:清单 1. 使用 API 层策略TransactionAttribute(TransactionAttributeType.REQUIRED)public void processTrade(TradeData trade) throws Exception try /first validate and insert the trade TraderData trader = service.getTrader(trade.getTraderID(); validateTraderEntitlements(trade, trader); verifyTraderLimits(trade, trader); performPreTradeCompliance(trade, trader); service.insertTrade(trade); /now adjust the account AcctData acct = service.getAcct(trade.getAcctId(); verifyFundsAvailability(acct, trade); adjustBalance(acct, trade); service.updateAcct(trade); /post processing performPostTradeCompliance(trade, trader); catch (Exception up) ctx.setRollbackOnly(); throw up; 注意在 清单 1 中,所有的处理都包含在 Java Transaction API (JTA) 事务的作用域内,包括所有的确认、验证和兼容性检查(提前和事后)。如果您通过探查器工具来运行 processTrade() 方法,那么就会看到每个方法调用的执行时间将与表 1 相似: 表 1. API 层方法探查 事务作用域方法名称执行时间 (ms)service.getTrader() 100validateTraderEntitlements() 300verifyTraderLimits() 500performPreTradeCompliance() 2300service.insertTrade() 200service.getAcct() 100verifyFundsAvailability() 600adjustBalance() 100service.updateAcct() 100performPostTradeCompliance() 1800processTrade() 方法的持续时间稍微长于 6 秒 (6100 ms)。由于事务的起始时间与方法相同,因此事务的持续时间也是 6100 ms。根据您所使用的数据库类型以及特定的配置设计,您将在事务执行过程中保持共享和专用锁(从执行读取操作开始)。此外,在由 processTrade() 方法调用的方法中执行的任何读取操作也可以在数据库中保持一个锁。您可能会猜想,在本例中,在数据库中保持锁持续 6 秒以上将不能扩展以支持高用户负载。清单 1 中的代码在没有高用户并发性或高吞吐量需求的环境中可能会非常出色地运行。遗憾的是,这只是大多数人用于测试的一种环境。一旦此代码进入生产环境,其中数以百计的交易者(或者是全球的)都在进行交易,则该系统最有可能会运行得非常糟糕,并且极有可能会遇到数据库死锁(根据您所使用的数据库而定)。现在,我将修复 清单 1 中的代码,方法是应用 High Concurrency 事务策略的先读取技巧。在 清单 1 所示的代码中,第一个要注意的地方是总共只用了 300 ms 的更新操作(插入和更新)。(此处,我假定 processTrade() 方法调用的其他方法不执行更新操作。基本技巧是在事务作用域之外执行读取操作和非更新处理,并且仅将更新封装在事务内部。清单 2 中的代码演示了减小事务作用域并仍然维持原子性的必要性:清单 2. 使用 High Concurrency 策略(先读取技巧)public void processTrade(TradeData trade) throws Exception UserTransaction txn = null; try /first validate the trade TraderData trader = service.getTrader(trade.getTraderID(); validateTraderEntitlements(trade, trader); verifyTraderLimits(trade, trader); performPreTradeCompliance(trade, trader); /now adjust the account AcctData acct = service.getAcct(trade.getAcctId(); verifyFundsAvailability(acct, trade); adjustBalance(acct, trade); performPostTradeCompliance(trade, trader); /start the transaction and perform the updates txn = (UserTransaction)ctx.lookup(UserTransaction); txn.begin(); service.insertTrade(trade); service.updateAcct(trade); mit(); catch (Exception up) if (txn != null) try txn.rollback(); catch (Exception t) throw up; throw up; 注意,我将 insertTrade() 和 updateAcct() 方法移动到了 processTrade() 方法的末尾,并将它们封装在了一个编程事务中。通过这种方法,所有读取操作和相应的处理将在事务的上下文之外执行,因此不会在事务持续时间内在数据库中保持锁。在新代码中,事务持续时间只有 300 ms,这显著低于 清单 1 中的 6100 ms。再次,其目标是减少在数据库中花费的时间,从而减少数据库的总体并发性,以及应用程序处理较大并发用户负载的能力。通过使用 清单 2 中的代码将数据库占用时间减少至 300 ms,从理论上说,吞吐量将实现 20 倍的提升。如表 2 所示,在事务作用域中执行的代码至减少至 300 ms:表 2. API 层方法探查 修改后的事务作用域方法名称执行时间 (ms)service.insertTrade() 200service.updateAcct() 100虽然这从数据库并发性的角度来说是一种显著的改善,但先读取技巧带来了一个风险:由于为更新指定的对象上没有任何锁,因此任何人都可以在此 LUW 过程中更新这些未锁定的实体。因此,您必须确保被插入或更新的对象一般情况下不会由多个用户同时更新。在之前的交易场景中,我做了一个安全的假设,即只有一个交易者会在特定的时间操作特定的交易和帐户。但是,并非始终都是这种情况,并且可能会出现失效数据异常。另外需要注意:在使用 Enterprise JavaBeans (EJB) 3.0 时,您必须通知容器您计划使用编程事务管理。为此,您可以使用 TransactionManagement(TransactionManagementType.BEAN) 注释。注意,这个注释是类级的(而不是方法级的),这表示您不能在相同的类中结合 Declarative 和 Programmatic 事务模型。选择并坚持其中之一。低级技巧假设您希望坚持使用 Declarative Transaction 模型来简化事务处理,但是仍然能在高用户并发性场景中增加吞吐量。同时,您应该在这种事务策略中使用低级技巧。通过此技巧,您通常会遇到与先读取技巧相同的折衷问题:读取操作通常是在事务作用域的外部完成的。并且,实现这种技巧最有可能需要代码重构。我仍然从 清单 1 中的示例入手。不用在相同的方法中使用编程事务,而是将更新操作移动到调用栈的另一个公共方法中。然后,完成读取操作和处理时,您可以调用更新方法;它会开始一个事务,调用更新方法并返回。清单 3 演示了这个技巧:清单 3. 使用 High Concurrency 策略(低级技巧)TransactionAttribute(TransactionAttributeType.SUPPORTS)public void processTrade(TradeData trade) throws Exception try /first validate the trade TraderData trader = service.getTrader(trade.getTraderID(); validateTraderEntitlements(trade, trader); verifyTraderLimits(trade, trader); performPreTradeCompliance(trade, trader); /now adjust the account AcctData acct = service.getAcct(trade.getAcctId(); verifyFundsAvailability(acct, trade); adjustBalance(acct, trade); performPostTradeCompliance(trade, trader); /Now perform the updates processTradeUpdates(trade, acct); catch (Exception up) throw up; TransactionAttribute(TransactionAttributeType.REQUIRED)public void processTradeUpdates(TradeData trade, AcctData acct) throws Exception try service.insertTrade(trade); service.updateAcct(trade); catch (Exception up) ctx.setRollbackOnly(); throw up; 通过此技巧,您可以有效地在调用栈的较低层次开始事务,从而减少花费在数据库中的时间。注意,processTradeUpdates() 方法仅更新在父方法(或以上)中修改中创建的实体。再次,保持事务的时间不再是 6 秒,您只需要 300 ms。现在是最难的部分。与 API 层策略或 Client Orchestration 策略不同,High Concurrency 策略并未使用一致的实现方法。这便是 图 1 看上去为何像一名经验丰富的曲棍球员(包括缺少的牙齿)的原因。对于一些 API 调用,事务可能会在 API 层的末端开始,而其他时候,它可能仅限于 DAO 层(特别是对于 LUW 中的单表更新)。技巧是确定在多个客户机请求之间共享的方法,并确保如果某个事务是在较高级的方法中开始的,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论