分布式架构全局异常处理_分布式(微服务)架构中的全局数据一致性

分布式架构全局异常处理

我已经在Github上发布了一个通用的JCA资源适配器,可以从Maven获得( ch.maxant:genericconnector-rar )并获得Apache 2.0许可 。 这使您可以将诸如REST和SOAP Web服务之类的内容绑定到Java EE应用程序服务器控制下的JTA事务中。 这样就可以很容易地构建能够保证数据一致性的系统,并使用尽可能少的样板代码。 请务必阅读常见问题解答

想象以下情况...

功能要求

  • ……多页的诱导睡眠需求……
  • FR-4053:当用户单击“购买”按钮时,系统应预订票证,与收单方进行确认的付款交易,并向客户发送一封信,其中包括他们的票证和收据。
  • …更多要求…

选定的非功能要求

  • NFR-08:必须使用NBS(公司“ Nouvelle预订系统”)(内部网中部署的HTTP SOAP Web服务)预订票。
  • NFR-19:必须使用COMS完成输出管理(打印和发送信件),COMS是在内联网中也部署的JSON / REST服务。
  • NFR-22:必须使用合作伙伴的MMF(快速赚钱)系统付款,该系统部署在Internet上并通过VPN连接。
  • NFR-34:系统必须将销售订单记录写入其自己的Oracle数据库。
  • NFR-45:与单个销售订单相关的数据必须在系统,NBS,COMS和MMF之间保持一致。
  • NFR-84:必须使用Java EE 6实施该系统,以便可以将其部署到我们的群集应用程序服务器环境中。
  • NFR-99:由于有MIGRATION'16项目,因此必须将系统构建为可移植到其他应用程序服务器。

分析

NFR-45很有趣。 我们需要确保多个系统之间的数据保持一致,即即使在软件/硬件崩溃期间也是如此。 但是NFR-08,NFR-19,NFR-22和NFR-34使事情变得有些困难。 SOAP和REST不支持事务! –不,那不是完全正确的。 我们可以很容易地使用JBoss的Arjuna事务管理器之类的东西,它支持WS-AT 。 例如,请参见该项目 (或其在Github上的源代码)JBoss示例或实际上是Metro示例 。 但是,这些解决方案存在一些问题:NFR-99(使用的API不可移植); NFR-19(REST不支持WS-AT,尽管JBoss 还在筹备中) 并且我们正在集成的Web服务甚至可能不支持WS-AT。 过去,我已经集成了许多内部和外部Web服务,但是从未遇到过支持WS-AT的服务。

多年来,我从事的项目具有相似的要求,但产生了不同的解决方案。 我曾经听说过一些公司,他们最终有效地构建了自己的事务管理器,这些事务管理器将Web服务绑定到事务中。 我也遇到过一些公司,它们不担心一致性而忽略了NFR-45。 我喜欢一致性的想法,但是我不喜欢业务项目编写一个框架来跟踪事务状态并手动提交或回滚它们以保持与Java EE事务管理器同步的想法。 因此,几年前,我对如何满足所有这些要求有了一个想法,但又避免了像构建事务管理器那样的复杂解决方案。 由于Java EE应用服务器支持分布式事务,因此NFR-84几乎可以解救。 我之所以写“几乎”是因为缺少某种形式的适配器,用于将非标准资源(例如Web服务)绑定到此类事务中。 但是Java EE规范还包含JSR-112 (用于构建资源适配器的JCA规范),可以将其绑定到分布式事务中。 我的想法是构建一个通用的资源适配器,该适配器可用于在应用程序服务器的控制下将Web服务和其他东西绑定到事务中,而所需的配置却很少,并且可以设计的API简单。

分布式事务的背景

为了更好地理解这个主意,让我们看一下分布式事务和两阶段提交 ,可以使用SQL将对数据库的调用绑定到XA事务 。 清单1显示了在XA事务中提交数据所需的语句列表:

mysql> XA START 'someTxId';

mysql> insert into person values (null, 'ant');

mysql> XA END 'someTxId';

mysql> XA PREPARE 'someTxId';

mysql> XA COMMIT 'someTxId';

mysql> select * from person;
+-----+-------------------------------+
| id  | name                          |
+-----+-------------------------------+
| 771 | ant                           |
+-----+-------------------------------+

清单1:SQL中的XA事务

全局事务的分支在第1行的数据库(资源管理器)中启动。可以使用任何任意事务ID,通常,应用程序服务器内部的全局事务管理器会生成此ID。 第3行是执行“业务代码”的地方,即与我们为什么使用数据库有关的所有语句,即插入数据并运行查询。 一旦完成所有业务工作,并且在第1行和第5行之间,您可能会调用其他远程资源,那么该事务将在第5行结束。但是请注意,该事务尚未完成,只是进入了全局状态。事务管理器可以开始查询每个资源管理器是否应该继续并提交事务。 如果只有一个资源管理器决定它不想提交数据,则事务管理器将告诉所有其他资源管理器回滚其事务。 但是,如果所有资源管理器都报告他们很高兴提交事务,并且通过对第7行的响应来这样做,那么事务管理器将使用以下命令告诉所有资源管理器提交其本地事务。 9.第9行运行后,第11行的select语句演示了该数据对所有人开放。

两阶段提交与分布式环境中的一致性有关。 我们不仅需要查看开心的流程,还需要了解上述每条命令之后在故障期间会发生什么。 如果直到并包括prepare语句的任何一条语句失败,则将回滚全局事务。 资源管理器和事务管理器都应将其状态写入持久性持久日志,以便在重新启动它们时可以继续执行过程并确保一致性。 直到并包括prepare语句,如果资源管理器失败并重新启动,它们将回滚事务。

如果某些资源管理器报告他们准备好提交,但是其他资源管理器报告他们想回滚,或者实际上其他人不回答,那么事务将被回滚。 如果资源管理器崩溃并变得不可用,可能需要一些时间,但是全局事务管理器将确保所有资源管理器回滚。

但是,一旦所有资源管理器成功报告了他们要提交的内容,便没有回头路了。 事务管理器将尝试在所有资源管理器上提交事务,即使它们暂时不可用。 结果是,暂时有其他事务可以查看的数据不一致,例如,一个崩溃的资源管理器尚未提交,即使它已经重新启动并再次可用,但最终,数据将变得一致。 这是很重要的一点,因为我经常听到,甚至我曾经引用两阶段协议保证ACID一致性。 它不能保证最终的一致性,只有本地交易被视为具有ACID属性。

两阶段提交协议中有一个更重要的步骤,即恢复,必须针对故障情况实施该恢复。 当事务管理器或资源管理器不可用时,事务管理器的工作是继续尝试,直到最终整个系统再次变得一致为止。 为此,它可以查询资源管理器以找到资源管理器认为不完整的事务。 在Mysql中,相关的命令及其结果显示在清单2中,即事务未完成。 我在清单1中的commit命令之前运行了该命令。在提交之后,结果集为空,因为资源管理器会在其自身之后清除并删除成功的事务。

mysql> XA RECOVER ;
+----------+--------------+--------------+----------+
| formatID | gtrid_length | bqual_length | data     |
+----------+--------------+--------------+----------+
|        1 |            8 |            0 | someTxId |
+----------+--------------+--------------+----------+

清单2:SQL中的XA恢复命令

设计

JCA规范包括事务管理器能够从适配器检索XA资源的能力,该资源表示理解启动,结束,准备,提交,回滚和恢复等命令的资源。 面临的挑战是利用这一事实来创建资源适配器,该资源适配器可以调用服务,这些服务具有与远程数据库引擎类似的方式可以提交和回滚。 如果我们做一些假设,我们可以定义一个简化的合同,这些服务需要实现,以便我们可以将它们绑定到分布式事务中。

考虑例如NFR-22和MMF,获取者系统。 通常情况下,付款系统可让您保留资金,然后不久便进行预订。 想法是,您致电他们的服务部门以确保有可用资金并预留了一些资金,然后您完成了所有业务交易,然后在数据提交后确定预订了预留的资金(有关其他方法,请参阅FAQ) 。 )。 预订和肯定的预订应该不超过几秒钟。 在崩溃的情况下,保留和释放保留不应超过几分钟。 根据我的经验,在您愿意承担费用的同时,可以预订机票并不久之后确认机票的机票预订系统也是如此。 我将初始阶段称为执行 ,将后期称为commit 。 当然,如果您不能自己完成业务,可以运行第二阶段的替代方法,即rollback ,以取消预订。 如果您不够友好,无法回滚,而只是保留预订,则提供者最终应让预订超时,以便保留的“资源”(在此示例中为钱或票)可以在其他地方使用,例如,以便客户可以去购物,也可以被其他人买票。

我们希望将这种系统绑定到我们的交易中。 此类服务的合同如下:

  1. 提供者应该提供三个操作:执行,提交和回滚(尽管commit实际上是可选的1 ),
  2. 提供者可以让未提交和未回滚的执行超时,然后在其他事务中使用任何保留的资源,
  3. 成功执行将确保只要没有发生超时2 ,就允许事务管理器提交或回滚保留的资源2
  4. 可以多次进行提交或回滚保留的调用,而不会产生副作用(在此处考虑幂等性),这样,如果初始尝试失败,则事务管理器可以完成事务。

脚注1:有时,Web服务会提供执行操作和取消呼叫的操作,例如,确实不会从客户帐户中提取资金。 但是他们不提供提交执行的操作。 如果我们回到清单2的讨论中,我曾说过事务最终是一致的,而不是立即一致的,那么很显然,全局事务中的系统是否肯定在执行阶段预订资源而不是等待就无关紧要了。直到提交阶段。 最终,要么所有系统都将提交,要么全部都将回滚并且货币交易将被取消,从而释放客户帐户上的预留资金。 但是请注意,提供所有三个操作的服务会更干净,并且如果有可能影响系统设计,建议确保集成的服务提供所有三个操作:执行,提交和回滚。

脚注2:成功调用execute操作后,Web服务可能不会由于陈述业务规则而拒绝提交或回滚事务。 它可能只是由于技术问题而暂时失败,在这种情况下,事务管理器可能会在不久后再次尝试完成操作。 将业务规则构建到提交或回滚操作中是不可接受的。 所有验证必须在执行期间完成,即在提交或回滚之前。 在数据库世界中也是如此—在XA事务期间,数据库必须最迟在准备阶段(即肯定在提交或回滚阶段之前)检查所有约束。

让我们将使用这样的合同与使用数据库进行比较。 以收单行Web服务为例:在执行过程中保留的资金实际上已放在一边,而其他尝试创建信用卡交易的实体不再可用。 但是钱也没有转入我们的帐户。 共有三种状态:i)款项在客户的贷方中; ii)这笔钱是保留的,不得用于其他交易; iii)这笔钱已被预订,客户无法再使用。 这类似于数据库事务:i)尚未插入行; ii)该行已插入,但当前对其他事务不可见(尽管这取决于事务隔离级别); iii)最后,该行已提交并且对所有人可见。 尽管这类似于收购方的示例,但在执行阶段提交后,在Web服务中保留资金的交易在整个世界立即可见–直到提交阶段之后,它才像数据库一样保持不可见。 隔离级别不同,但是当然可以构建Web服务来隐藏此类信息(例如,根据状态隐藏)(如果需要的话)。

使用这样的合同,WS-AT和两阶段提交的设计方式存在一些根本差异。 首先,封装在Web服务中的事务在执行和提交/回滚之间不会保持打开状态。 其次,由于事务未保持打开状态,因此资源没有被锁定,就像使用数据库时那样。 这两个差异导致了第三点:回滚Web服务调用通常不是要撤消其工作,而是要更改其工作状态,以便从业务角度来看,资源再次可用。

这些差异使通用连接器优于传统的两阶段提交。 该连接器真正发生的事情是,我们背负着分布式事务,精心挑选了最佳部分,即执行,提交,回滚和恢复。 通过在Java EE应用程序服务器中执行此操作,我们可以免费获得事务管理!

最后阶段是必需的,即恢复 (请参见清单1和清单2),但是不一定要由Web服务实现,因为适配器可以在内部处理该部分-因为适配器知道了交易的状态,因此一直在调用Web服务。

因此,根据上述假设,我们可以构建一个通用的JCA资源适配器,该适配器可跟踪事务状态并在正确的时间调用Web服务上的提交/回退操作,当事务管理器告诉XA资源执行启动事务,执行一些业务代码并提交或回滚事务。

适用于微服务架构

与单片应用程序相比,微服务体系结构或SOA确实存在一个值得注意的问题,即难以在分布式系统中保持数据的一致性。 微服务通常将提供用于完成工作的操作,但也应提供用于取消该工作的操作。 不必使该工作不可见,但就业务而言,确实需要将其取消,这样就无需在该工作上投入更多的资源(金钱,时间,人力等)。 此处提供的适配器可以在“应用程序层”内使用,即您的客户端(移动,富Web客户端等)调用的服务。 该层应部署在Java EE应用服务器中,并在每次调用环境中的微服务之一时使用通用连接器。 这样,如果发生故障,事务管理器可以“回滚”所有微服务调用。 应用程序层的重点是控制全局事务,以便事务管理器可以监视和协调需要一致执行的所有事情,而不必说直接从客户端调用每个微服务,然后必须编写清理代码并恢复一致性。

使用适配器

我给自己的第一个要求是构建一个API,该API允许您在现有事务中向Web服务添加业务调用。 清单3显示了一个示例,该示例如何使用Java 8 lambda将Web服务调用绑定到事务中(即使API与Jav​​a 1.6兼容–参见Github)。

@Stateless
public class SomeServiceThatBindsResourcesIntoTransaction {

  @Resource(lookup = "java:/maxant/BookingSystem")
  private TransactionAssistanceFactory bookingFactory;
...
  public String doSomethingInvolvingSeveralResources(String refNumber) {
...
    BookingSystem bookingSystem = new BookingSystemWebServiceService()
                                        .getBookingSystemPort();
...
    try ( ...
      TransactionAssistant bookingTransactionAssistant = 
                                bookingFactory.getTransactionAssistant();
... ) {
      //NFR-34 write sales data to Oracle using JDBC and XA-Driver
      ...

      //NFR-08 book tickets
      String bookingResponse = 
          bookingTransactionAssistant.executeInActiveTransaction(txid -> {

        return bookingSystem.reserveTickets(txid, refNumber);
      });
...
      return response;
    } catch (...) {...}
...
  }
...

清单3:将Web服务调用绑定到事务中

第1行将类指定为EJB,默认情况下使用容器管理的事务,并且要求在每个方法调用中都存在一个事务,如果不存在,则开始一个事务。 第4-5行确保将资源适配器的相关类的实例注入到服务中。 第9行创建一个Web服务客户端的新实例。 此客户机代码是使用wsimport和WSDL服务定义生成的。 第13-14行创建了资源适配器提供的“事务助手”。 然后在第21行使用该助手在事务中运行第23行。 在后台,这将设置XA资源,事务管理器将使用该资源来提交或回滚连接。 第23行返回一个String ,该String在第20行上同步设置String

将此代码与写入数据库进行比较:第4和第5行就像注入DataSourceEntityManager ; 第9和13-14行类似于打开与数据库的连接; 最后,第21-23行就像调用执行一些SQL一样。

第23行不执行任何错误处理。 如果Web服务引发异常,则会导致事务回滚。 如果您决定捕获此类异常,则需要记住抛出另一个异常,以使容器回滚该事务,或者您需要通过在会话上下文上调用setRollbackOnly()来设置要回滚的事务(演示代码位于Github展示了一个捕获SQLException的示例。

因此,将Web服务调用绑定到事务中的开销非常小,类似于在数据库上执行某些SQL。 重要的是,在上面的应用程序代码中看不到提交或回滚。 但是,我们仍然需要向应用服务器展示如何提交和回滚。 每个Web服务仅完成一次,如清单4所示。

@Startup
@Singleton
public class TransactionAssistanceSetup {
...
  @Resource(lookup = "java:/maxant/BookingSystem")
  private TransactionAssistanceFactory bookingFactory;
...
  @PostConstruct
  public void init() {
    bookingFactory
      .registerCommitRollbackRecovery(new Builder()
      .withCommit( txid -> {
        new BookingSystemWebServiceService()
          .getBookingSystemPort().bookTickets(txid);
      })
      .withRollback( txid -> {
        new BookingSystemWebServiceService()
          .getBookingSystemPort().cancelTickets(txid);
      })
      .build());
...
  }

  @PreDestroy
  public void shutdown(){
        bookingFactory.unregisterCommitRollbackRecovery();
...
  }

清单4:一次性注册回调以处理提交和回滚

在这里,第1-2行告诉应用程序服务器创建一个单例并在应用程序启动后立即执行。 这很重要,因此,如果资源适配器需要恢复可能不完整的事务,则可以在准备就绪后立即恢复。 第5-6行与清单3中的行类似。第11行是在资源适配器中注册回调的位置,以便它了解如何在Web服务中提交和回滚事务。 我也在这里使用过Java 8 lambda,但是如果您使用的是Java 6/7,则可以使用匿名内部类代替第11行上的新构建器。第13-14行只需调用Web服务来预订先前的票证保留,在清单3的第23行。如果事务管理器决定回滚全局事务,则第17-18行取消保留的票证。 非常重要的是,第26行在应用程序关闭时注销了适配器实例的回调。 这是必需的,因为适配器仅允许您为每个JNDI名称(Web服务)注册一个回调,并且如果在不注销回调的情况下重新启动了应用程序,则第11行将失败,并在第二次注册回调时发生异常。

如您所见,使用我创建的通用适配器,将Web服务或实际上不自然支持事务的任何事物绑定到JTA全局事务中非常容易。 剩下的唯一事情就是配置适配器,以便可以将其与应用程序一起部署。

适配器配置

每个Web服务需要配置一次适配器,并将其绑定到事务中。 为了更清楚一点,请考虑清单4中的代码,用于注册提交和回滚的回调。 每个适配器实例只能注册一个回调,即JNDI名称。 配置适配器是特定于应用程序服务器的,但这仅是因为您放置了以下XML。 在向上的Jboss EAP 6 / Wildfly 8中,将其放入<jboss-install-folder>/standalone/configuration/standalone.xml ,类似于<subsystem xmlns="urn:jboss:domain:resource-adapters:...>

<resource-adapters>
  <resource-adapter id="GenericConnector.rar">
    <archive>
      genericconnector-demo-ear.ear#genericconnector.rar
    </archive>
    <transaction-support>XATransaction</transaction-support>
    <connection-definitions>
      <connection-definition 
          class-name=
            "ch.maxant.generic_jca_adapter.ManagedTransactionAssistanceFactory" 
          jndi-name="java:/maxant/BookingSystem" 
          pool-name="BookingSystemPool">
        <config-property name="id">
          BookingSystem
        </config-property>
        <config-property name="handleRecoveryInternally">
          true
        </config-property>
        <config-property name="recoveryStatePersistenceDirectory">
          ../standalone/data/booking-tx-object-store
        </config-property>
        <xa-pool>
          <min-pool-size>1</min-pool-size>
          <max-pool-size>5</max-pool-size>
        </xa-pool>
        <recovery no-recovery="false">
          <recover-credential>
            <user-name>asdf</user-name>
            <password>fdsa</password>
          </recover-credential>
        </recovery>
      </connection-definition>
      ... one connection-definition per registered commit/rollback callback
    </connection-definitions>
  </resource-adapter>
</resource-adapters>

清单5:配置通用资源适配器

清单5从第2-35行的资源适配器的定义开始。 归档文件在第4行中定义-注意EAR文件名和RAR文件名之间的哈希符号。 请注意,您可能还需要将Maven版本号粘贴在RAR文件名中。 这取决于您EAR中的物理文件以及JBoss以外的其他应用服务器可能使用不同的约定。 第6行告诉应用程序服务器使用适配器中的XAResource ,以便将其绑定到XA事务中。 然后,需要对要集成的每个Web服务重复行8-32行。 第9和10行定义了资源适配器提供的工厂,该值应始终为ch.maxant.generic_jca_adapter.ManagedTransactionAssistanceFactory 。 第11行定义了用于在EJB中查找资源的JNDI名称。 第12行为用于连接定义的池命名。 建议每个连接定义使用唯一的名称。 第13-15行定义了连接定义的ID。 每个连接定义必须使用唯一的名称。 第16-18行告诉资源适配器内部跟踪事务状态,以便资源适配器无需集成的Web服务的帮助即可处理恢复。 默认值为false,在这种情况下,您必须在清单4中注册恢复回调-请参阅下面的清单6。 如果将资源适配器配置为内部处理恢复,则需要19-21行-您必须提供目录路径,在该目录中应写入需要跟踪的事务状态。 建议在运行应用程序服务器的本地计算机上使用目录,而不要使用网络上的目录。 JBoss需要22-31行,以便它确实使用XAResource并将调用绑定到全局事务中。 其他应用程序服务器可能仅需要第6行-尚未完全测试到其他应用程序服务器的部署( 更多详细信息…… )。

复苏

到目前为止,关于恢复我还没有说太多。 的确,清单5中XML中的handleRecoveryInternally属性意味着应用程序开发人员实际上不需要考虑恢复。 但是,如果我们返回清单2,则恢复显然是两阶段提交协议的一部分。 实际上,维基百科指出:“ 为了适应从故障中恢复(在大多数情况下是自动的),协议的参与者使用协议状态的日志记录。 协议恢复过程使用日志记录,这些记录通常生成速度较慢,但​​可以在故障中幸免。 ”。 参与者是资源管理器,例如数据库或Web服务,或者如果您希望这样解释的话,可能是资源适配器。 老实说,我不完全理解为什么事务管理器不能这样做。 为了使资源适配器更加灵活,而且在不允许您将适配器写入文件系统的情况下(大公司的运营管理部门往往严格要求如此),也可以提供资源适配器带有一个回调,以便它可以向Web服务询问Web服务感觉不完整的交易编号数组。 请注意,如果适配器的配置如上所述,则它将跟踪对Web服务本身的调用状态。 收到成功响应后,已调用Web服务的commit或rollback方法的信息将保存到磁盘。 如果应用程序服务器在写入信息之前崩溃了,那并不是那么悲惨,因为适配器将告知事务管理器事务未完成,并且事务管理器将再次尝试使用Web服务进行提交/回滚。 由于上面定义的Web服务合同要求可以多次调用提交和回滚方法而不会引起问题,因此,当事务管理器随后尝试重新提交或重新回滚事务时,这绝对没有问题。 这使我指出,您要注册恢复回调的唯一原因是不允许您将资源适配器写入磁盘。 但是我应该指出,我不能完全理解为什么XA要求资源管理器在确定事务管理器本身能够跟踪此状态时提供可能未完成的事务的列表。

设置恢复,以便适配器使用Web服务来查询它认为不完整的事务,首先需要将部署描述符中的handleRecoveryInternally属性设置为false (此后您无需提供recoveryStatePersistenceDirectory属性),然后添加一个恢复回调,如清单6所示。

@Startup
@Singleton
public class TransactionAssistanceSetup {

  @Resource(lookup = "java:/maxant/Acquirer")
  private TransactionAssistanceFactory acquirerFactory;
...
  @PostConstruct
  public void init() {
    acquirerFactory
      .registerCommitRollbackRecovery(new Builder()
      .withCommit( txid -> {
...
      })
      .withRollback( txid -> {
...
      })
      .withRecovery( () -> {
        try {
          List<String> txids = new AcquirerWebServiceService().getAcquirerPort().findUnfinishedTransactions();
          txids = txids == null ? new ArrayList<>() : txids;
          return txids.toArray(new String[0]);
        } catch (Exception e) {
          log.log(Level.WARNING, "Failed to find transactions requiring recovery for acquirer!", e);
          return null;
        }
      }).build());
...

清单6:定义恢复回调

注册恢复回调是在注册清单4中设置的提交和回滚回调之后进行的。清单6的第18-26行添加了恢复回调。 在这里,我们仅创建一个Web服务客户端并调用该Web服务以获取应完成的交易ID列表。 任何错误都将被简单地记录下来,因为事务管理器很快就会出现并再次询问。 一个更可靠的解决方案可能选择通知管理员此处是否有错误,因为首先错误不应在此处发生,其次事务管理器从后台任务调用此回调,在该任务中不会向用户显示错误。 当然,如果该Web服务当前不可用,则事务管理器将不会收到任何事务ID,但是希望下次尝试时(在JBoss Wildfly 8中大约每两分钟一次),该Web服务将再次可用。

测验

为了测试适配器,基于本文开头描述的场景构建了一个演示应用程序(也可以在Github上获得),该应用程序调用三个Web服务并两次写入数据库,所有这些都在同一事务中进行。 获取方支持执行,提交,回滚和恢复; 预订系统支持执行,提交和回滚; 信作者仅支持执行和回滚。 在测试中,该过程首先写入数据库,然后调用收单方,预订系统,信函编写器,最后更新数据库。 这样,可以测试过程中多个点的故障。 使用以下测试案例对适配器进行了测试:

  • 正面测试用例 –此处所有内容都可以通过。 之后,将检查日志,数据库和Web服务,以确保确实已提交所有内容。
  • 由于违反数据库外键约束,导致流程结束时失败 –在这里,Web服务已全部执行其业务逻辑,并且该测试可确保在数据库失败后,事务管理器回滚Web服务调用。
  • 在执行Acquisitionr Web服务期间失败 –在这里,该失败发生在初始数据库插入之后,以检查该插入是否已回滚
  • 执行预订Web服务期间失败 –在这里,该失败发生在初始数据库插入和Web服务调用获取方以检查是否都回滚之后
  • 执行来信书写器Web服务期间失败 –在这里,该失败发生在初始数据库插入和两个Web服务调用以检查是否全部三个回滚之后
  • 在提交过程中,Web服务被关闭 –通过在提交回调中设置断点,我们可以取消部署Web服务,然后继续该过程。 在Web服务上的初始提交失败,但是数据库正常并且数据可用。 但是在重新部署Web服务并使其启动并运行之后,事务管理器再次尝试执行应该成功的提交。
  • 在提交期间,数据库将关闭 -同样使用断点,在提交之前数据库也将关闭。 提交可在Web服务上运行,但无法在数据库上运行。 重新启动数据库后,下次事务管理器运行恢复时,它将提交数据库。
  • 在准备过程中,在提交之前杀死应用程序服务器 –在这里,我们检查是否没有任何提交。
  • 在提交期间杀死应用程序服务器 –我们这里检查服务器重新启动并运行恢复后,是否恢复了所有系统之间的一致性,即是否已提交所有内容。

结果

演示应用程序和资源适配器会记录他们所做的所有事情,因此调用的第一个端口是在每次测试期间读取日志。 另外,数据库写入磁盘,因此我们可以使用数据库客户端查询数据库状态,例如, select * from person p inner join address a on a.person_FK = p.id; 。 获取方将文件夹~/temp/xa-transactions-state-acquirer/写入。 如果事务未完成,则存在一个名为exec*txt的文件,否则,如果分别为提交或回滚,则将其分别命名为commit*txtrollback*txt 。 预订系统将写入<jboss-install>/standalone/data/bookingsystem-tx-object-store/文件夹。 信作者将写到文件夹<jboss-install>/standalone/data/letterwriter-tx-object-store/ 。 提交或回滚事务后,适配器将删除名为exec*txt的临时文件,因此验证完成的唯一方法是读取适配器日志,尽管检查是否已删除文件是有道理的,尽管不会通知是否存在是一次提交或回滚。

尽管Mysql中的一个古老的bug克服了一个很小的挑战,但结果还是令人鼓舞的, 并且符合预期 ,我将在另一篇文章中进行介绍。 如果您在使用数据库时遇到困难,请查看JBoss手册该手册提供了有关如何在不同数据库上使用XA恢复的提示。

常问问题

  • 我正在集成的服务仅提供要执行的操作和要取消的操作。 There is no commit operation. No worries – this is acceptable and discussed above, where the contract that web services should fulfil is discussed. Basically, call the execute operation during normal business processing and the cancel operation only if there is a rollback. During the commit stage, don't do anything, since data was already committed during the call to the execute operation.
  • What happens if a web service takes a long time to come back online, after a business operation is executed but before the commit/rollback operation has been called? Transactions that require recovery may end up in trouble if they take a long time to come back online, because it is recommended that the systems behind the web service implement a timeout after which they clean up reserved but not booked (committed) resources. Take the example where a seat is reserved in a theatre during the execution but the final booking of the seat is delayed due to a system failure. It is entirely possible that the seat will be released after say half an hour so that it can be sold to other potential customers. If the seat is released and some time later the application server which reserved it attempts to book the seat, there could be an inconsistency in the system as a whole, as the other participants in the global transaction could be committed, indicating that the seat was sold, and for example money was taken for the seat, yet the seat has been sold to another customer. This case can occur in normal two phase commit processes. Imagine a database transaction that creates a foreign key reference to a record, but that record is deleted in a different transaction. Normally the solution is to lock resources, which the reservation of the seat is actually doing. But indefinate locking of resources can cause problems like deadlocks. This problem is not unique to the solution presented here.
  • Why don't you recommend WS-AT? Mostly because the world is full of services which don't offer WS-AT support. And the adapter I have written here is generic enough that you could be integrating non-web service resources. But also because of the locking and temporal issues which can occur, related to keeping the transaction open between the execution and commit stages.
  • Why not just create an implementation of XAResource and enlist it into the transaction using the enlistResource method? Because doing so doesn't handle recovery. The generic connecter presented here also handles recovery when either the resource or the application server crash during commit/rollback.
  • This is crazy – I don't want to implement commit and rollback operations on my web services! WS-AT is for you! Or an inconsistent landscape…
  • I'm in a microservice landscape – can you help me? 是! Rather than letting your client call multiple microservices and then having to worry about global data consistency itself, say in the case where one service call fails, make the client call an “application layer”, ie a service which is running in a Java EE application sever. That service should make calls to the back end by using the generic connector, and that way the complex logic required to guarantee global data consistency is handled by the transaction manager, rather than code which you would have to otherwise write.
  • The system I am integrating requires me to call its commit and rollback methods with more than just the transaction ID. You need to persist the contextual data that you use during the execution stage and use the transaction ID as the key which you can then use to lookup that data during commit, rollback or recovery. Persist the data using an inner transaction ( @RequiresNew ) so that the data is definitely persisted before commit/rollback/recovery commences – this way it is failure resistant.
  • The system I am integrating dictates a session ID and does not take a transaction ID. See the previous answer – map the transaction ID to the session ID of the system you are integrating. Ensure that you do it in a peristent manner so that your application can survive crashes.
  • The payment system I am integrating executes the payment on their own website, but the “commit” occurs over an HTTP call. Can I integrate this? 是! Redirect to their site to do the payment; when they callback to your site, run your business logic in a transaction and using the transaction assistant execute a no-op method in the execution stage which will cause the commit callback to be called at commit time; in the commit callback make the HTTP call to the payment system to confirm the payment.

Using the generic adapter in your project

To use the adapter in your application you need to do the follwing things:

  • Create a dependency on the ch.maxant:genericconnector-api Maven module,
  • Write code as shown in listing 3 to execute business operations on the web services that your application integrates,
  • Setup commit and rollback callbacks as shown in listing 4, and optionally a recovery callback as shown in listing 6,
  • Configure the resource adapter as shown in listing 5
  • Deploy the resource adapter in an EAR by adding a dependency to the Maven module ch.maxant:genericconnector-rar and referencing it as a connector module in the application.xml deployment descriptor.

For more information, see the demo application .

结论

The idea that I had, namely to bind web service calls into JTA transactions using a generic JCA resource adapter does work. It eliminates the need to build your own transaction management logic and it does ensure that there is consistency across the entire landscape, regardless of whether a transaction is committed or rolled back in the application code running in the Java EE application server.

进一步阅读

With thanks to Claude Gex for his review,

翻译自: https://www.javacodegeeks.com/2015/08/global-data-consistency-in-distributed-microservice-architectures.html

分布式架构全局异常处理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值