我最近发表了 一篇 关于如何将非事务性资源(如 Web 服务/微服务)绑定到全局分布式事务中以便自动处理恢复的文章。多年来,我经常不得不将“非事务性”系统集成到 Java EE 应用程序服务器中,而数据一致性通常是讨论的话题,甚至是非功能性需求。我将“非事务性”用引号引起来,因为系统通常包含确保数据一致性的方法,例如使用调用来补偿,但这些系统并不是您通常所说的事务性系统。当然没有办法配置 Java EE 应用程序服务器来自动处理此类资源的恢复。
以下是我们编制的模式列表,显示了在面对集成非事务系统的任务时保持一致性的不同方法。
-
将作业写入数据库
- 您希望在销售完成后发送电子邮件确认的常见场景。您不能发送电子邮件,然后尝试将销售交易提交到您的数据库,因为如果提交失败,客户会收到一封电子邮件,说明他们已经购买了东西,而您没有任何记录。您不能在销售交易提交到您的数据库后发送电子邮件,因为如果电子邮件发送失败(例如,邮件服务器暂时关闭),客户将不会得到他们的确认,也许还有一个指向工单的链接他们买了。一种解决方案是将需要发送电子邮件的事实写入数据库中,以保持销售的同一交易。然后,批处理或
@scheduled
ejb 可以定期检查它是否应该发送电子邮件。成功发送电子邮件后,它会更改记录的状态,以便不再发送电子邮件。同样的问题适用于这里,您可能只能发送电子邮件但不能更新数据库。但是如果你能够读取数据库,你很可能能够更新它,并且由于数据库故障而发送两次相同的电子邮件并不像从未发送它那样糟糕,如果你没有发送它可能会出现这种情况'不处理异步发送电子邮件。像这样集成的一个缺点是,这意味着您无法集成需要结果的系统,以便在回复用户之前继续处理您的业务逻辑。您必须异步处理集成。 - jms - 在与先前解决方案类似的场景中,您可以发送包含作业的 jms 消息,而不是将作业写入数据库。 jms 是事务性的,但是是异步的,因此该解决方案具有与上述解决方案相同的缺点。如果你当时不能处理工作,而不是改变要完成的工作的状态,你将消息发送回带有属性的队列,这样它只在一定时间后才被处理,或者你发送将消息发送到死信队列以进行手动处理。
- 通用连接器(jca 适配器) ——我最近发表了一篇 博客文章 ,描述了我创建的通用 jca 资源适配器,它可以让您将典型的非事务性资源(如 Web 服务)绑定到 jta 事务中。有关详细信息,请参阅博客文章。使用通用连接器意味着事务管理器将在事务需要提交、回滚或恢复时执行回调,这样您只需要编写响应这些事件的应用程序代码。
-
cdi 事件
- 在字段上使用
@inject @qualifier event<t>
&field.fire(t);
当你想在方法参数上触发事件 &@observes(during=transactionphase.after_failure) @qualifier t
,在事务失败后,将为每个触发的事件调用该方法。这样您就可以在交易失败时实施一些补偿。同样,您可以使用不同的事务阶段来做不同的事情,例如after_success
执行调用以确认初始预订。我们甚至使用这些机制来延迟对远程系统的调用,例如在提交之前将工作发布到工作流引擎,这样我们就可以确保复杂过程中的所有验证逻辑在远程系统调用之前完成制成。见下文第 12 条。 - 自定义解决方案 ——如果你真的可以证明 if,那么你可以构建带有超时等的复杂代码,涉及使用远程资源处理提交、回滚和恢复事务的批处理和脚本。您需要问自己的问题是您是编写业务代码的专家,还是有效编写事务管理器的专家。
- 业务流程引擎 ——现代引擎可以将各种远程资源集成到业务流程中,并且它们倾向于处理故障恢复之类的事情。他们通常会重试失败的调用,并且他们可以在远程系统再次联机期间持久地处理进程状态,以便可以恢复进程。 bpel 不支持提交和回滚,而是支持补偿以保证整个环境的一致性。
- atomikos & tcc - 一种能够将 Web 服务绑定到 jta 事务中的产品。据我所知,它是一个独立的事务管理器,可以在 Java EE 应用程序服务器之外运行。但我没有使用该产品的经验。
- ws-at - 使用专有配置(和/或注释),您可以设置两个应用程序服务器以在全局事务中完成它们的工作。虽然这听起来很有希望,但我还没有遇到一个实施 ws-at 的高效系统。实际上只支持 soap web 服务,尽管 jboss 在管道中有一些支持 rest 服务的东西 。
- ejb - 远程 ejbs:Java EE 应用程序服务器已经能够将事务上下文从一个服务器传播到另一个服务器相对较长的时间。如果您需要调用恰好使用 java ee 堆栈实现的服务,为什么不使用远程 ejb 调用它而不是通过 web 服务调用它,这样您就可以免费将服务绑定到全局事务中?本地 ejbs:如果您调用的服务恰好是使用 say ejb 技术用 java 编写的,为什么不直接在本地部署它而不是付出额外的努力通过 soap web 服务远程调用它呢?您可能会从企业架构师那里得到加分,但是是否将可扩展性和可组合性与性能、一致性和简单性进行了比较?当然,具有微服务等趋势的现代架构意味着部署大量远程服务是件好事,但总是需要权衡取舍,在决定需要远程访问景观的哪些部分时,您需要真正理解它。
- 事务回调 - 与解决方案 4 类似,但使用 事务同步 api 来注册在事务的相关阶段调用的回调。与 cdi 事件不同,这里的问题是您不知道提交或回滚事务的上下文,因为回调没有传递相关数据,这与传递到 cdi 中观察方法的对象不同.因此,如果您需要补偿交易并调用网络服务来取消您在交易期间所做的事情,您从哪里获得需要这样做的数据?
-
将 xa 资源登记到事务中
- 添加
xaresource
接口的自定义实现,您可以使用
enlistresource
方法将其登记到事务中。不幸的是,提交/回滚方法只被调用一次,如果它们失败了,它们将不会在恢复期间再次被调用。 - 非事务性资源最后 - 如果没有其他模式可以实现,并且您不需要在过程中的特定时间调用该资源,例如您需要发送电子邮件作为事务的一部分,但它不需要无论您将其作为第一个还是最后一个流程步骤来执行,都应始终在流程结束时立即调用它,即在事务提交之前不久。与远程系统调用失败的可能性相比,事务无法提交的可能性相对较小(特别是如果所有 sql 都已刷新到数据库)。如果调用失败,则回滚事务。如果调用成功,则提交事务。如果事务在提交期间失败,并且补偿非事务资源对您来说很重要,您将需要使用上述模式之一来为系统添加一些补偿。
下表总结了解决方案。恢复列表示该解决方案支持的自动恢复级别。同步性列表示如果您需要响应以便继续处理,您是否可以使用该解决方案,在这种情况下,您需要一个同步解决方案。这里的同步性与阻塞与非阻塞无关,而是与时间以及是否需要响应以完成处理活动有关。
脚注:
- 手动恢复——您需要对处理失败时的操作进行编程,即在将工作放入“死信队列”之前应尝试重试的频率。
- 如果您将队列配置为持久队列,jms 将自动尝试重新发送消息。但是对于处理消息失败的尝试,您如何处理取决于您,程序员。
- 事务管理器将不断尝试提交/回滚未完成的事务,直到管理员介入处理长时间运行的故障。
- 回调只被调用一次,所以你只有一次机会
- 业务流程引擎将反复尝试重新调用失败的 Web 服务调用。补偿也是如此。该行为通常是可配置的。
- 远程 ejbs:jta 事务被传播到其他应用程序服务器,因此协调事务管理器将事务恢复传播到绑定到事务中的其他应用程序服务器。本地 ejbs:使用本地 ejbs 意味着它们对数据库所做的任何调用都将在与您的应用程序代码相同的事务中处理。如果本地 ejb 使用不同的数据库,你应该为所有数据库、消息队列等使用 xa 驱动程序,以便事务管理器可以使用两阶段提交来确保系统范围的一致性。
在所有这些中,我目前最喜欢的是 通用连接器 。它支持需要响应的呼叫,以及全自动恢复。这意味着我可以专注于编写业务代码,而不是真正属于框架的样板代码。
如果您知道更多方法,请与我联系或发表评论,以便我将它们添加到列表中。