REST微服务间的事务?
比方说,我们有一个用户,电子钱包REST微服务和一个粘合在一起的API网关。 当Bob在我们的网站上注册时,我们的API网关需要通过用户微服务和钱包通过钱包微服务来创build用户。
下面是一些情况可能出错的情况:
-
用户Bob创build失败:没关系,我们只是将错误消息返回给Bob。 我们正在使用SQL事务,所以没有人在系统中看到Bob。 一切都很好:)
-
用户Bob已创build,但在创build电子钱包之前,我们的API网关硬件崩溃。 我们现在有一个用户没有钱包(不一致的数据)。
-
用户Bob已创build,并且在我们创build电子钱包时,HTTP连接将断开。 钱包创build可能已经成功,或者可能没有成功。
有什么解决scheme可以防止这种数据不一致的发生? 是否有模式允许交易跨越多个REST请求? 我已经阅读了关于这个问题上的两阶段提交维基百科页面,但我不知道如何在实践中应用它。 这个primefaces分布式事务:一个RESTfuldevise文件也似乎很有趣,虽然我还没有读过它。
另外,我知道REST可能不适合这个用例。 也许正确的方法来处理这种情况,完全放弃REST,并使用不同的通信协议,如消息队列系统? 还是应该在应用程序代码中执行一致性(例如,通过检测不一致性并修复这些不一致的后台作业,或者通过在“用户”模型上使用“创build”值,“创build”值等)具有“状态”属性?
什么没有意义:
- 与REST服务的分布式事务 。 REST服务按照定义是无状态的,所以它们不应该是跨越多个服务的事务边界的参与者。 您的用户注册用例场景很有意义,但使用REST微服务来创build用户和电子钱包数据的devise并不好。
什么会让你头痛:
- 分布式事务的EJB 。 这是那些在理论上起作用,但不是在实践中的东西之一。 现在我正在尝试通过JBoss EAP 6.3实例为远程EJB创build分布式事务。 我们已经和RedHat的支持人员谈了好几个星期了,但还没有成功。
- 使用WS-AT的 SOAP服务 。 这是一个包含多个移动部件的解决scheme,在行业中用处不大,在您的平台上可能不支持。
- 一般的阶段提交解决scheme 。 我认为2PC协议是一个很好的algorithm(很多年前我用RPC在C中实现它)。 它需要全面的故障恢复机制,包括重试,状态库等等。所有的复杂性都隐藏在事务框架中(例如:JBoss Arjuna)。 但是,2PC不是失败的certificate。 有些情况下交易根本无法完成。 然后,您需要手动识别并修复数据库不一致问题。 如果幸运的话,可能会发生在一百万次交易中,但每100次交易中可能会发生一次,具体取决于您的平台和场景。
- 补偿交易 。 对于SOAP服务,如果您有BPEL引擎,则可以从这种替代方法中受益,这是长时间运行事务的更好select。 还有其他支持薪酬的框架/平台,但BPEL可能是最常见的一种。 但是补偿也不是失败的certificate。 你可能仍然以不一致(=头痛)。
什么可能是最好的select:
- 最终的一致性 。 类似ACID的分布式交易和补偿性交易都不能作为失败证据,并且两者都可能导致不一致。 最终的一致性往往比偶尔的不一致更好,所以你可以使用asynchronous通信来创build一个更健壮的解决scheme。 在您的场景中,当Bob注册时,API网关可以将消息发送到NewUser队列,然后马上回复用户说:“您将收到一封确认创build帐户的电子邮件”。 队列消费者服务可以处理消息,在单个事务中执行数据库更改,并将电子邮件发送给Bob以通知创build帐户。
但是如果你需要同步响应呢?
- 改造微服务 。 如果带有队列的解决scheme因为服务使用者需要立即响应而无法工作,那么我宁愿将用户和电子钱包function重新configuration到同一服务中(或者至less在同一个虚拟机中,以避免分布式事务)。 是的,它离微服务更近一步,更接近巨石,但会使您免于一些头痛。
所有分布式系统都有事务一致性的问题。 做到这一点的最好方法就像你说的,有一个两阶段的提交。 让钱包和用户在待处理状态下创build。 创build完成后,请单独调用以激活用户。
最后一次呼叫应该是安全可重复的(以防连接断开)。
这将需要最后一次调用了解这两个表(以便它可以在一个JDBC事务中完成)。
或者,你可能想考虑一下为什么你对没有钱包的用户感到担心。 你相信这会造成问题吗? 如果是这样,也许把这些作为单独的rest电话是一个坏主意。 如果一个用户不应该存在没有钱包,那么你可能应该添加钱包给用户(在原来的POST调用来创build用户)。
这是我最近在采访中被问及的一个经典问题。如何调用多个Web服务,并在任务中间保留某种error handling。 今天,在高性能计算中,我们避免了两阶段提交。 我多年前读了一篇关于交易所谓的“星巴克模型”的文章:想想在星巴克订购,付款,准备和接受你订购的咖啡的过程…我简单地把事情简单化了,但是两阶段提交模式会build议整个过程将是所有步骤的单一包装交易,直到你收到你的咖啡。 但是,通过这种模式,所有的员工都会等到停止工作,直到拿到咖啡。 你看到的图片?
相反,“星巴克模型”通过遵循“尽力而为”模型并弥补过程中的错误而提高了生产力。 首先,他们确保你付出! 然后,有消息队列与您的订单附加到杯子。 如果在这个过程中出现了问题,就像你没有拿到咖啡一样,这不是你要求的等等,我们进入了补偿过程,我们确保你得到你想要的或者退款。这是最有效的模式提高生产力。
有时候,星巴克正在浪费咖啡,但整个过程是高效的。 在构buildWeb服务时还有其他一些技巧,比如deviseWeb服务的方式可以调用任意次数,并提供相同的最终结果。 所以,我的build议是:
-
在定义你的networking服务时,不要太在意(我不相信现在发生的微服务炒作:太多的风险太多);
-
asynchronous提高性能,所以更喜欢asynchronous,尽可能通过电子邮件发送通知。
-
构build更智能的服务,使其“可回顾”任意次数,使用uid或taskid进行处理,这些uid或taskid将遵循自下而上的顺序,直到最后,在每个步骤中validation业务规则;
-
使用消息队列(JMS或其他),并转移到error handling处理器,通过应用相反的操作来应用操作来“回滚”,顺便说一下,使用asynchronous顺序将需要某种队列来validation进程的当前状态,所以考虑一下;
-
在最后的手段(因为它可能不经常发生),把它放在队列中手动处理错误。
让我们回到最初发布的问题。 创build一个帐户,并创build一个钱包,并确保一切都完成了。
比方说一个Web服务被调用来编排整个操作。
Web服务的伪代码如下所示:
1-Call帐户创build微服务,传递一些信息和一些独特的任务ID 1.1帐户创build微服务将首先检查该帐户是否已经创build。 任务ID与帐户的logging相关联。 微服务检测到该帐户不存在,所以它创build它并存储任务ID。 注意:这个服务可以被称为2000次,它总是会执行相同的结果。 该服务回答“收据包含最less的信息,在需要时执行撤消操作”。
2-Call电子钱包的创build,给它的账户ID和任务ID。 假设一个条件无效,钱包创build不能执行。 调用返回一个错误,但没有创build任何东西。
3 – 编排者被告知错误。 它知道它需要中止帐户创build,但它不会自己做。 它将要求钱包服务通过在步骤1结束时收到的“最小撤销收据”来执行此操作。
4-账户服务读取撤消收据并知道如何撤消操作; 撤消收据甚至可能包含有关其他微服务的信息,这些微服务本可以称之为部分工作。 在这种情况下,撤消收据可能包含账户ID,并可能包含执行相反操作所需的一些额外信息。 在我们的情况下,为了简化事情,比方说,简单地删除帐户使用其帐户ID。
5-现在,假设Web服务从未收到成功或失败(在这种情况下)帐户创build撤消被执行。 它只会再次调用帐户的撤消服务。 而这种服务通常应该永远不会失败,因为它的目标是不再存在帐户。 所以它会检查它是否存在,并且看不到任何东西可以撤消它。 所以它返回的操作是成功的。
6- Web服务返回给用户,该帐户无法创build。
这是一个同步的例子。 如果我们不希望系统完全恢复错误,我们可以用一种不同的方式pipe理它,并把案例放到一个针对服务台的消息队列中。“我见过这种情况是在一个公司不够完善可以向后台系统提供钩子以纠正情况,服务台收到的消息包含成功执行的消息,并且有足够的信息来修复事件,就像我们的撤消收据一样,可以以完全自动化的方式使用。
我已经执行了search,微软网站有这种方法的模式描述。 这就是所谓的补偿交易模式:
补偿交易模式
恕我直言,微服务架构的关键方面之一是交易仅限于个人微服务(单一责任原则)。
在当前的例子中,用户创build将是一个自己的事务。 用户创build会将USER_CREATED事件推入事件队列。 电子钱包服务将订阅USER_CREATED事件并进行电子钱包的创build。
如果我的钱包只是用户在同一sql数据库中的另一堆logging,那么我可能会将用户和钱包创build代码放在同一服务中,并使用普通数据库事务工具来处理。
听起来你在问钱包创build代码要求你触摸另一个系统或系统时会发生什么? 我说这一切都取决于创造过程是多么复杂和有风险。
如果只是接触另一个可靠的数据存储(比如不能参与sql事务的数据存储),那么根据整个系统参数,我可能愿意承担第二次写入不会发生的极小的可能性。 我可能什么也不做,只是通过补偿性交易甚至一些特别的方法来提出exception,处理不一致的数据。 正如我总是告诉我的开发人员:“如果这种事情发生在应用程序,它不会被忽视”。
随着钱包创build的复杂性和风险的增加,您必须采取措施来改善所涉及的风险。 让我们说一些步骤需要调用多个合作伙伴apis。
此时,您可能会引入一个消息队列以及部分构build的用户和/或钱包的概念。
确保您的实体最终得到正确构build的一个简单而有效的策略是让作业重试直到成功,但是很大程度上取决于应用程序的用例。
我也会长时间思考为什么我的configuration过程中出现了一个容易出错的步骤。
有什么解决scheme可以防止这种数据不一致的发生?
传统上,使用分布式事务pipe理器。 几年前,在Java EE世界中,您可能已经将这些服务创build为部署到不同节点的EJB ,并且您的API网关将远程调用这些EJB。 应用程序服务器(如果configuration正确)使用两阶段提交自动确保事务在每个节点上提交或回滚,以保证一致性。 但是这要求所有服务都部署在同一types的应用程序服务器上(因此它们是兼容的),实际上只有一个公司部署的服务可以使用。
是否有模式允许交易跨越多个REST请求?
对于SOAP(好吧,不是REST),有WS-AT规范,但是我没有必须整合的服务支持。 对于REST,JBoss有一些东西正在stream行 。 否则,“模式”是要么find一个产品,你可以插入到你的架构,或build立自己的解决scheme(不推荐)。
我已经为Java EE发布了这样的产品: https : //github.com/maxant/genericconnector
根据您参考的论文,还有Try-Cancel / Confirm模式以及来自Atomikos的相关产品。
BPEL引擎使用补偿来处理远程部署的服务之间的一致性。
另外,我知道REST可能不适合这个用例。 或许正确的方法来处理这种情况,完全放弃REST并使用不同的通信协议,如消息队列系统?
将非事务性资源“绑定”到事务中有很多种方法:
- 正如你所build议的那样,你可以使用一个事务性的消息队列,但是它会是asynchronous的,所以如果你依赖于响应,就会变得混乱。
- 你可以写下这样一个事实,即你需要将后端服务调用到数据库中,然后使用批处理调用后端服务。 再次,asynchronous,所以可以得到凌乱。
- 您可以使用业务stream程引擎作为您的API网关来编排后端微服务。
- 您可以使用远程EJB,因为它支持开箱即用的分布式事务。
还是应该在应用程序代码中执行一致性(例如,通过检测不一致性并修复这些不一致的后台作业,或者通过在“用户”模型上使用“创build”值,“创build”值等)具有“状态”属性?
玩恶魔主张:为什么要build立类似的东西,什么时候有产品为你做(见上面),并且可能比你做得更好,因为它们经过了试验和testing?
就我个人而言,我喜欢微服务这个由用例定义的模块的概念,但正如您的问题所提及的,它们对于银行,保险,电信等传统业务有适应性问题。
像许多人提到的那样,分布式交易不是一个好的select,现在人们为了最终一致的系统而做出更多的select,但是我不确定这对于银行,保险等等是有效的。
我写了一个关于我提出的解决scheme的博客,可能是这可以帮助你….
为什么不使用支持脚本/编程的APIpipe理(APIM)平台? 因此,您将能够在APIM中构build组合服务,而不会影响微服务。 为此我devise了使用APIGEE。