高性能集群性能指标_高性能战略

到目前为止,在本系列文章中 ,您已经学习了如何实现三种交易策略:

  • API层策略,适用于具有过程粒度的API层的业务应用程序
  • 客户端编排策略,适用于具有更细粒度API层的业务应用程序
  • 高并发策略,适用于具有高并发用户负载的应用程序

在最后一部分中,我将介绍一种事务处理策略,该策略对于具有非常高性能要求的应用程序很有用。 与“高并发”策略一样,“高性能”策略也需要权衡考虑。

为了确保高度的数据完整性和一致性,必须进行事务处理。 但是交易也很昂贵; 它们消耗宝贵的资源,并可能降低应用程序的速度。 当你有一个高速的应用,其分秒必争,你可以通过实现高性能事务策略保持ACID(原子性,一致性,隔离性和持久性)特性在一定程度上。 正如您将在本文中看到的那样,“高性能”策略不像其他事务策略那样健壮,对于所有涉及高性能应用程序的用例,它也不是最佳选择。 但是肯定有很多时候,这种策略将使您能够保持最快的处理时间,并且仍然支持一定程度的数据完整性和一致性。

当地交易和补偿框架

从数据持久性的角度来看,执行数据库更新操作的最快方法是将本地事务与数据库存储过程结合使用。 本地事务(有时称为数据库事务 )是由数据库而不是容器环境管理的事务。 你并不需要的代码在你的应用程序的任何事务逻辑(如@Transactional在Spring注解或@TransactionAttribute在EJB 3注释)。

存储过程是快速的,因为它们是预编译的并且驻留在数据库服务器上。 他们不需要高性能的策略,以及它们的有效性和性能都引起的一些有趣的争论(见“那么,数据库存储过程好事还是坏事?”在链接相关主题 )。 使用存储过程可能会降低应用程序的可移植性,增加复杂性并降低整体敏捷性。 但是它们通常比基于Java的JDBC SQL语句快,并且当性能比可移植性和维护更重要时,它们是一个不错的选择。 也就是说,您当然可以根据需要将任何基于JDBC的框架与纯SQL一起使用。

如果本地交易如此之快,那么为什么每个人都不会使用它们呢? 主要原因是,除非使用连接传递之类的技术,否则您无法维护传统的ACID事务属性。 对于本地事务,数据库更新操作被视为单个工作单元,而不是一个整体。 本地事务的另一个限制是您不能将它们与传统的对象关系映射(ORM)框架一起使用,例如Hibernate,TopLink或OpenJPA。 您只能使用基于JDBC的框架,例如iBATIS或Spring JDBC(请参阅参考资料 ),或者您自己的自制数据访问对象(DAO)框架。

高性能策略基于本地交易的使用。 但是,请等待-如果“本地交易”模型不支持基本的ACID属性,那么基于该模型的交易策略怎么可能是一个好的策略? 答案是,高性能策略将本地交易模型与薪酬框架结合起来。 每个单独的数据库更新操作都独立存在,但是在发生错误的情况下,补偿框架可确保单个事务被撤消,从而保持原子性和一致性。

图1说明了在没有补偿框架的情况下使用本地事务时发生的情况。 请注意,当第三个存储过程失败时,逻辑工作单元(LUW)结束,并且数据库处于不一致状态,仅应用了三个更新中的两个:

图1.没有补偿框架的本地交易
没有补偿框架的本地交易

有了补偿框架,当发生错误时,成功的事务将被撤消,从而形成一致的数据库。 图2说明了与图1相同的错误发生时(概念上)发生的情况。请注意,一旦存储过程成功返回,它将向补偿框架注册。 当第三个存储过程失败时,它将触发一个事件,告诉补偿框架撤消此补偿范围中包括的所有内容。

图2.带有补偿框架的本地交易
有补偿框架的本地交易

此技术通常称为宽松ACID 。 它是一种典型的事务解决方案,适用于诸如使用业务流程执行语言(BPEL)进行流程编排或使用Web服务的面向服务的体系结构中的长期事务这样的用例。 在这种情况下,一个事务性工作单元可能需要几分钟,几小时甚至在几天内才能完成。 假设您可以锁定资源这么长时间是不现实的。 另外,很难(有时是不可能)在某些异构平台上或通过HTTP(例如Web服务)传播事务。

宽松ACID的概念也可以应用于短期交易。 对于高性能交易策略,交易持续时间以秒为单位而不是分钟。 但是,同样的原则适用-在极端高性能的情况下,您希望最大化数据库并发性并最小化等待时间和处理时间。 此外,您想利用最快的方式执行数据库更新操作。 这是通过使用本地事务和数据库存储过程来实现的。 补偿框架仅在发生错误时提供帮助; 当一切正常时,它不会妨碍您。 数据库并发达到最大程度,数据库更新操作将以最快的方式执行,并且在发生错误的情况下,补偿框架将为您做好一切。 听起来像个必杀技,不是吗? 好吧,不幸的是,事实并非如此。

权衡与问题

许多权衡和问题与此交易策略相关联。 请记住,它为您提供了最快的方式来执行事务并在某种程度上仍保持ACID属性。 您放弃了事务隔离,健壮性和简单性。 仅当使用本系列介绍的其他三种策略之一无法获得所需的性能时,才应使用此策略。 无论您是自己构建补偿框架,还是使用可用的开源或商业解决方案之一(本文将在稍后介绍),补偿框架都是复杂且风险很大的。

与此策略相关的最大问题可能是普遍缺乏鲁棒性和数据一致性,这是大多数基于补偿的解决方案的典型特征。 因为没有事务隔离,所以每个数据库更新操作都被视为一个单独的工作单元。 因此,另一个工作单元可能会对处理中的数据采取措施。 如果在LUW期间发生错误,则可能为时已晚,无法撤消更新。 或者,更典型地,撤消更新可能导致级联问题。 例如,假设您正在处理一个非常大的订单,这将耗尽该项目的库存。 在处理过程中,将触发一个事件(因为在LUW期间订单已提交到数据库),以自动向供应商发送消息以补充该商品。 如果在订单处理过程中发生错误,则补偿框架将撤消交易,但是订单的影响(即补充库存消息)已经发送给供应商,导致该特定项目的库存过多。 如果保留了传统的ACID属性,则在完成订购过程的整个LUW之前,不会发送要补充库存的事件消息。 这只是说明即使在使用补偿框架来维护事务原子性的情况下如何发生数据不一致的众多示例之一。

某些业务状况或技术不适合采用高并发交易策略。 特别是,当使用补偿框架和宽松的ACID时,异步处理方案处于高风险中。 在某些情况下,您可能需要以某种程度的性能来换取较慢的基于ACID的交易策略。 此策略的另一个折衷方案是您不能利用基于ORM的持久性框架,而持久性框架需要程序化或声明性事务模型。 这并不限制您使用原始JDBC编码; 您可以使用很多基于JDBC的框架,包括iBATIS(一种开源SQL映射框架)和Spring JDBC。 或者,您可以使用自己的基于DAO的自定义框架。 ORM限制可能要求您接受另一个折衷方案–可维护性和技术选择,以在事务支持下获得更好的性能。

尽管通过使用补偿框架可以保持某种程度的数据库一致性,但是此策略会带来高度的风险。 在事务撤消过程中可能会发生错误,从而使数据库处于不一致状态。 在这种情况下,某些数据库更新操作可能会撤消,而其他数据库更新操作则不能撤消,有时需要手动干预才能解决问题。 因此,在单个LUW中几乎没有数据库更新操作的应用程序是此事务处理策略的理想选择。 另外,使用这种策略的应用程序通常在交织的LUW中没有共享实体的使用,这意味着很少有多个用户同时作用于同一实体(例如帐户,客户或订单)。 这些特征减少了由于不一致和缺乏事务隔离而导致灾难性结果的机会。

适用于该特定交易策略的应用程序相当强大,错误很少发生(错误率小于10%)。 执行交易补偿是一项昂贵且费时的操作。 如果您一直在逆转数据库更新操作,则与使用其他事务策略之一相比,系统将变慢,并且总体性能可能会更慢。 而且执行补偿性更新的要求越多,数据库不一致的风险就越大。 选择此交易策略之前,请务必分析您的错误率。

本文的其余部分描述了现有的薪酬框架,并显示了使用自定义解决方案的简单实现,以说明我所描述的概念。

现有薪酬框架

Java平台内提供了两个补偿框架:扩展事务的J2EE活动服务(JSR 95)和JBoss业务活动框架(请参阅参考资料 )。 它们提供注册,消息传递和补偿触发逻辑(但不提供更新反向方法本身)。 就像同一概念中描述的补偿框架,即不同的问题侧栏一样,这些框架通常与长时间运行的事务或基于Web的请求相关联,因此难以与高性能事务策略一起使用。 因此,使用此交易策略时,您很可能会创建自己的自定义补偿框架。

尽管J2EE活动服务规范主要针对应用服务器供应商,但是您可以将相同的概念应用于您自己的自定义补偿框架。 因此,在本节中,我将为您简要介绍J2EE活动服务,以便您了解薪酬框架的运作方式。

用于扩展事务的J2EE活动服务基于OMG活动服务(请参阅参考资料 )。 J2EE活动服务规范定义了一组接口和类,用于协调和控制活动中动作的执行。 活动是一组已注册的操作 (例如数据库更新操作)。 活动由控制器控制和协调,该控制器通常是作为第三方插件组件实现的可插入协议。 每个活动都包含一个信号集 ( javax.activity.SignalSet ),该信号集将信号 ( javax.activity.Signal )发送到每个已注册的动作。 图3显示了使用补偿时会发生什么的概念图:

图3. J2EE Activity Service概念图
JSR-95概念图

活动必须在控制器(或更具体地说是控制器内的薪酬管理器组件)中注册。 活动完成后,它将向控制器发送信号(在这种情况下为成功或失败)。 如果控制器从活动接收到成功信号,则它将信号发送到协调器组件(在本例中为PROPAGATE),从而调用下一个活动。 注意,在图3的步骤8中,FAILURE信号被发送回控制器。 在这种情况下,控制器会向协调器发出“失败”信号,从而以相反的顺序调用补偿活动。 尽管未在图3中显示,但控制器还监视补偿活动和协调器之间来回的信号,以确保逆转活动也成功完成。

实施自定义补偿框架

编写自定义补偿框架听起来像是一项艰巨的任务,但这并不过分复杂-只是费时。 您可以使用普通的旧Java代码来实现自己的补偿框架,或者使用更复杂的技术。 本着简洁的精神,我将向您展示一个使用纯Java代码的简单示例,以便您理解概念。 我将把创造力和乐趣留给您。

无论您使用的是开源,商业还是自定义补偿框架,您仍然必须提供方法,SQL或存储过程来调用以撤消数据库更新操作。 这就是为什么我喜欢将存储过程与“高性能”策略一起使用的另一个原因。 它们相对容易分类,自成体系,并且使识别(和执行)补偿程序变得更加容易。 因此,我将在即将提供的示例中使用存储过程。

为了避免给您带来不必要的细节,我仅假设我已经准备好在数据库中准备以下存储过程:

  • sp_insert_trade (将新的股票交易订单插入数据库)
  • sp_insert_trade_comp (通过在数据库中执行删除操作来反转交易插入)
  • sp_update_acct (更新帐户余额以反映股票买卖)
  • sp_update_acct_comp (将帐户余额更新为最新更新之前的值)
  • sp_get_acct (从数据库获取帐户)

我还将跳过使用CallableStatement JDBC代码显示DAO类的过程,因此我可以专注于与该策略最紧密相关的代码。 (请参阅相关的主题为调用使用简单的JDBC存储过程引用和代码)。 因为自定义补偿协调器的实现趋于变化很多,并且可能很冗长,所以我将仅显示底层结构并提供有关如何填充其余实现代码的注释。

根据您实施策略的方式以及用于补偿更新的技术,用于控制更新反向操作的注释或逻辑可以在应用程序的API层或其DAO层中。 为了说明实现高性能策略的技术,我将使用一个简单的股票交易示例,其中协调补偿范围的逻辑位于应用程序的API层中。 在此示例中,有两个与股票交易相关的活动:将股票交易插入数据库(活动1),并更新帐户余额以反映股票交易(活动2)。 两种活动都使用本地事务和存储过程以单独的方法实现。 定制补偿协调器( CompController )负责管理补偿范围,并在发生错误时撤销活动。

清单1说明了不使用补偿交易的股票交易方法。 processTrade()方法引用的AcctDAOTradeDAO类包含JDBC逻辑,以执行我前面列出的存储过程。 为了简洁起见,我将跳过这些课程。

清单1.不补偿交易的股票交易示例
public class TradingService {

   private AcctDAO acctDao = new AcctDAO();
   private TradeDAO tradeDao = new TradeDAO();

   public void processTrade(TradeData trade) throws Exception {

      try {
         //adjust the account balance
         AcctData acct = acctDao.getAcct(trade.getAcctId());
         if (trade.getSide().equals("BUY")) {
            acct.setBalance(acct.getBalance()
               - (trade.getShares() * trade.getPrice()));
          } else {
            acct.setBalance(acct.getBalance()
               + (trade.getShares() * trade.getPrice()));
          }

          //insert the trade and update the account
          long tradeId = tradeDao.insertTrade(trade);
          acctDao.updateAcct(acct);

      } catch (Exception up) {
         throw up;
      }
   }
}

请注意, 清单1中缺少事务管理(没有编程或声明性事务注释或代码)。 如果在updateAcct()方法期间发生错误,则不会回滚insertTrade()方法插入的交易,从而导致数据库不一致。 尽管此代码很快,但是它不支持ACID事务属性。

要应用高性能交易策略,您首先需要创建(或使用)补偿框架来跟踪活动,并在发生错误时将其撤消。 清单2展示了一个自定义补偿协调器的简单示例,概述了创建自己的自定义补偿框架所需的步骤:

清单2.定制补偿框架示例
public class CompController {

   //contains activities and the callback method to reverse the activity
   private Map compensationMap;

   //contains a list of active compensation scopes and activity sequence numbers
   private Map compensationScope;

   public CompController() {
      //load compensation map containing callback classes and methods
   }

   public void startScope(String compId) {
      //send jms start message containing compId as JMSXGroupId
   }

   public void registerAction(String compId, String action, Object data) {
      //send jms data message containing sequence number and data
      //using compId as JMSXGroupID.
      //CompController manages sequence number internally using the
      //compensationScope buffer and stores in JMSXGroupSeq message field
   }

   public void stopScope(String compId) {
      //consume and remove all messages having compId as the JMSXGroupID
      //without taking action
      //remove compId entries from compensationScope buffer
   }

   public void compensate(String compId) {
      //consume all messages having compId as the JMSXGroupID and process in
      //reverse order
      //using the compensation map and reflection to invoke reversal methods
      //remove compId entries from compensationScope buffer
   }
}

compensationMap属性包含所有活动的预加载列表(按名称)以及反向活动的相应类和方法(按名称)。 此示例的内容可能包含以下条目: {"insertTrade", "TradeDAO.insertTradeComp"}{"updateAcct", "AcctDAO.updateAcctComp"}compensationScope属性包含一个按compId和到目前为止已注册的活动的活动补偿范围的列表。 该缓冲区用于获取registerAction()方法使用的下一个序列号。 其余方法很不言自明。

注意,我正在使用Java消息服务(JMS)消息传递来实现补偿协调器实现。 我之所以选择这种技术,主要是因为它提供了一种保证(尽管使用了持久消息和保证的传递)的方式,即在补偿期间发生故障的情况下,无法回滚的事务仍在JMS队列中,并且可以拾取并由另一个线程执行。 JMS消息传递还允许进行异步活动注册和补偿处理,从而进一步加快了应用程序源代码的速度。 当然,将补偿信息保留在内存中将大大加快处理速度,但如果补偿协调器发生故障,则可能导致数据库进一步不一致。

清单3中的源代码示例说明了将定制补偿框架应用于清单1中的原始应用程序源代码的技术:

清单3.具有补偿框架的股票交易示例
public class TradingService {

   private CompController compController = new CompController();
   private AcctDAO acctDao = new AcctDAO();
   private TradeDAO tradeDao = new TradeDAO();

   public void processTrade(TradeData trade) throws Exception {

      String compId = UUID.randomUUID().toString();
      try {
         //start the compensation scope
         compController.startScope(compId);

         //get the original account values and set the acct balance
         AcctData acct = acctDao.getAcct(trade.getAcctId());
         double oldBalance = acct.getBalance();
         if (trade.getSide().equals("BUY")) {
            acct.setBalance(acct.getBalance()
               - (trade.getShares() * trade.getPrice()));
         } else {
            acct.setBalance(acct.getBalance()
               + (trade.getShares() * trade.getPrice()));
         }

         //insert the trade and update the account
         long tradeId = tradeDao.insertTrade(trade);
         compController.registerAction(compId, "insertTrade", tradeId);

         acctDao.updateAcct(acct);
         compController.registerAction(compId, "updateAcct", oldBalance);

         //close the compensation scope
         compController.stopScope(compId);

      } catch (Exception up) {
         //reverse the individual database operations
         compController.compensate(compId);
         throw up;
      }
   }
}

在查看清单3时 ,请注意,在定义事务性工作单元时,首先要使用startScope()方法启动补偿范围。 然后,您必须保存原始余额,以便可以在注册活动时将其传递给协调员。 活动完成后,您可以使用registerAction()方法注册该活动。 这告诉补偿协调员,数据库更新操作已成功完成,需要将其添加到可能的补偿活动列表中。 如果整个LUW成功结束,则可以调用stopScope()方法,该方法将从补偿协调器中删除所有引用。 但是,如果发生异常,则可以调用compensate()方法,该方法将逆转已提交给数据库的所有活动。

清单23中的源代码当然还不能投入生产,但是确实说明了构建自己的补偿框架所涉及的技术。 您的自定义补偿框架可以使用自定义注释,方面(或拦截器),甚至可以使用您自己的自定义补偿域特定语言(DSL;请参阅参考资料 )来使代码更加直观。 另外,您不需要将JMS异步消息传递用于补偿框架,但是我发现它对于解决与补偿失败有关的问题很有用。

结论

您是否选择使用高性能策略归结为权衡。 此交易策略涉及许多风险,并且实施起来很复杂。 但是,如果性能是您的第一要务, 并且您的应用程序已经相当健壮且没有错误,那么这是一种合适的策略,可确保至少一定程度的数据库完整性和一致性,同时又不会对性能产生不利影响。 如果性能不是您最关心的问题,我会推荐这种解决方案吗? 当然不是。 您应该始终尝试在应用程序中使用传统的ACID属性。 但是,如果您愿意为了提高性能而牺牲某种程度的数据库一致性和数据完整性,则应考虑采用高性能策略。

在本系列中 ,我向您展示了与事务处理相关的一些陷阱和挑战,并介绍了四种可用于为您的应用程序构建可靠的事务解决方案的事务策略。 从外部看,事务处理看起来很简单,但是当您开始将其应用于各种业务应用程序场景时,它会变得非常复杂。 我在本系列文章中的目标是减少这种复杂性,并提出一些技术和策略,以简化被认为具有挑战性的任务-保持高度的数据完整性和一致性。 我希望这些交易文章从交易处理的角度为您提供了提高应用程序和数据的健壮性所需的知识。


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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值