假设我们有一个用户,电子钱包REST微服务和一个将各种东西粘合在一起的API网关。当Bob在我们的网站上注册时,我们的API网关需要通过用户微服务和钱包通过钱包微服务创建用户。
现在有几种情况可能会出错:
用户Bob创建失败:没问题,我们只是向Bob返回错误消息。我们正在使用SQL事务,因此没有人在系统中看到过Bob。一切都很好:))
用户Bob已创建,但在创建我们的电子钱包之前,我们的API网关很难崩溃。我们现在有一个没有钱包的用户(数据不一致)。
用户Bob已创建,在我们创建电子钱包时,HTTP连接断开。钱包创建可能已经成功,也可能没有。
有哪些解决方案可以防止这种数据不一致发生?是否存在允许事务跨越多个REST请求的模式?我已经阅读了Two-phase commit上的维基百科页面,该页面似乎触及了这个问题,但我不确定如何在实践中应用它。这篇Atomic Distributed Transactions: a RESTful design论文似乎也很有趣,虽然我还没读过它。
或者,我知道REST可能不适合这个用例。也许正确的方法来处理这种情况完全放弃REST并使用不同的通信协议,如消息队列系统?或者我应该在我的应用程序代码中强制执行一致性(例如,通过让后台作业检测到不一致并修复它们,或者在我的用户模型上使用" state"属性"创建" ,"创建"值等)?
答案 0 :(得分:117)
什么没有意义:
什么会让你头疼:
什么可能是最好的选择:
但是如果你需要同步响应呢?
答案 1 :(得分:49)
这是我最近在采访中被问到的一个经典问题如何调用多个Web服务,并且仍然在任务中保留某种错误处理。今天,在高性能计算中,我们避免了两阶段提交。多年前我读了一篇关于所谓的星巴克模型"对于交易:考虑订购,支付,准备和接收您在星巴克订购的咖啡的过程...我过分简化了事情但是两阶段提交模型表明整个过程将是涉及所有步骤的单一包装交易直到你收到你的咖啡。但是,使用这种模式,所有员工都会等待并停止工作,直到你拿到咖啡。你看到了这张照片吗?
相反,"星巴克模型"通过遵循"尽力而为"建模并补偿过程中的错误。首先,他们确保你支付!然后,有一些消息队列,您的订单附在杯子上。如果在这个过程中出现问题,就像你没有拿到咖啡,这不是你订购的等等,我们进入补偿过程,我们确保你得到你想要的或退款给你,这是最有效的模式提高生产力。有时,星巴克正在浪费咖啡,但整个过程非常有效。在构建Web服务时还需要考虑其他一些技巧,例如以可以被调用任意次数的方式进行设计,并且仍然提供相同的最终结果。所以,我的建议是:
在定义您的网络服务时不要太精细(我不相信这些天发生的微服务炒作:走得太远的风险太多);
异步提高了性能,因此更喜欢异步,尽可能通过电子邮件发送通知。
构建更智能的服务,使其成为可回收的"任何次数,使用uid或taskid进行处理将遵循订单自下而上直到最后,验证每个步骤中的业务规则;
使用消息队列(JMS或其他)并转移到错误处理处理器,这些处理器将操作应用于" rollback"通过应用相反的操作,顺便说一下,使用异步订单将需要某种队列来验证流程的当前状态,所以考虑一下;
最后,(因为它可能不经常发生),将其放入队列中以手动处理错误。
让我们回顾一下发布的初始问题。创建一个帐户并创建一个钱包并确保一切都已完成。
让我们说一个Web服务被调用来协调整个操作。
Web服务的伪代码如下所示:
呼叫帐户创建微服务,传递一些信息和一些独特的任务ID 1.1帐户创建微服务将首先检查该帐户是否已创建。任务ID与帐户的记录相关联。微服务检测到该帐户不存在,因此它创建它并存储任务ID。注意:此服务可以调用2000次,它将始终执行相同的结果。该服务以"收据回答,该收据包含在需要时执行撤销操作的最少信息"。
致电创建电子钱包,为其提供帐户ID和任务ID。让我们说条件无效并且无法执行钱包创建。该调用返回错误但未创建任何内容。
协调器被告知错误。它知道它需要中止帐户创建,但它本身不会这样做。它将要求钱包服务通过传递其最小撤消收据"在第1步结束时收到。
帐户服务读取撤销收据并知道如何撤消操作;撤销收据甚至可能包括有关另一个微服务的信息,它可以称自己为完成部分工作。在这种情况下,撤销收据可能包含帐户ID以及执行相反操作所需的一些额外信息。在我们的案例中,为简化起见,我们只想删除帐户ID。
现在,让我们说网络服务从未收到过执行或失败(在这种情况下)创建了帐户创建的撤消操作。它只会再次调用帐户的撤销服务。而且这项服务通常应该永远不会失败,因为它的目标是不再存在该帐户。因此它检查它是否存在并且看不到任何可以撤消它的工作。因此它返回操作成功。
网络服务向用户返回无法创建帐户的信息。
这是一个同步的例子。如果我们不希望系统完全恢复错误,我们可以以不同的方式管理它并将案例放入针对服务台的消息队列中。我已经看到这是在一家公司进行的,该公司没有足够的钩子可以提供给后端系统以纠正情况。服务台收到的消息中包含已成功执行的操作,并且有足够的信息来解决问题,就像我们的撤销收据可以完全自动化一样使用。
我已经进行了搜索,并且微软网站对此方法有一个模式描述。它被称为补偿交易模式:
答案 2 :(得分:26)
所有分布式系统都存在事务一致性问题。这样做的最好方法就像你说的那样,进行两阶段提交。将钱包和用户创建为挂起状态。创建后,单独调用以激活用户。
最后一次通话应该是安全可重复的(如果您的连接断开)。
这将需要最后一次调用知道两个表(以便它可以在单个JDBC事务中完成)。
或者,您可能想要考虑为什么您如此担心没有钱包的用户。你相信这会引起问题吗?如果是这样,可能将这些作为单独的休息呼叫是一个坏主意。如果用户不应该没有钱包,那么您应该将钱包添加到用户(在原始POST调用中创建用户)。
答案 3 :(得分:9)
在当前示例中,用户创建将是一个自己的事务。用户创建会将USER_CREATED事件推送到事件队列中。电子钱包服务将订阅USER_CREATED事件并创建电子钱包。
答案 4 :(得分:7)
如果我的钱包只是与用户在同一个sql数据库中的另一堆记录,那么我可能会将用户和钱包创建代码放在同一个服务中,并使用普通的数据库事务工具处理它。
听起来你在询问当钱包创建代码要求你触摸另一个系统或系统时会发生什么?我想这一切都取决于创作过程的复杂程度和风险程度。
如果只是触摸另一个可靠的数据存储区(比如一个不能参与你的sql事务的数据存储区),那么根据整个系统参数,我可能愿意承担第二次写入赢得的机会很小的风险。发生了。我可能什么都不做,但提出异常并通过补偿事务或甚至一些特殊方法处理不一致的数据。正如我总是告诉我的开发人员:“如果在应用程序中发生这种事情,它就不会被忽视”。
随着钱包创建的复杂性和风险的增加,您必须采取措施来改善所涉及的风险。假设某些步骤需要调用多个伙伴apis。
此时,您可能会引入一个消息队列以及部分构造的用户和/或钱包的概念。
确保您的实体最终构建正确的简单而有效的策略是让作业重试直到成功,但很大程度上取决于应用程序的用例。
我也会长时间地思考为什么我的配置过程中出现了一个容易出错的步骤。
答案 5 :(得分:3)
有哪些解决方案可以防止这种数据不一致发生?
传统上,使用分布式事务管理器。几年前,在Java EE世界中,您可能已经将这些服务创建为部署到不同节点的EJB,并且您的API网关将对这些EJB进行远程调用。应用程序服务器(如果配置正确)使用两阶段提交自动确保事务在每个节点上提交或回滚,从而保证一致性。但这需要将所有服务部署在相同类型的应用服务器上(以便它们兼容),并且实际上只能使用单个公司部署的服务。
是否存在允许事务跨越多个REST请求的模式?
对于SOAP(好的,不是REST),有WS-AT规范,但我没有必要集成的服务支持。对于REST,JBoss有something in the pipeline。否则,"模式"是找到可以插入架构的产品,或者构建自己的解决方案(不推荐)。
我已经为Java EE发布了这样的产品:https://github.com/maxant/genericconnector
根据您参考的论文,还有At-Iikikos的Try-Cancel / Confirm模式和相关产品。
BPEL Engines使用补偿处理远程部署的服务之间的一致性。
或者,我知道REST可能不适合这个用例。也许正确的方法来处理这种情况完全放弃REST并使用不同的通信协议,如消息队列系统?
"绑定"非交易资源进入交易:
或者我应该在我的应用程序代码中强制执行一致性(例如,通过让后台作业检测到不一致并修复它们,或者在我的用户模型上使用" state"属性"创建& #34;,"创建"值等)?
扮演恶魔主张:为什么会有这样的东西,当有产品为你做这些(见上文),并且可能做得比你好,因为它们经过了试验和测试?
答案 6 :(得分:2)
就个人而言,我喜欢微服务这个由用例定义的模块的概念,但正如你提出的问题,他们对银行,保险,电信等传统业务有适应性问题......
正如许多人提到的那样,分布式交易不是一个好的选择,人们现在更多地寻求最终的系统,但我不确定这对银行,保险等是否有用......
我写了一篇关于我提出的解决方案的博客,可能这可以帮助你......
答案 7 :(得分:2)
一个简单的解决方案是使用用户服务创建用户并使用消息总线,其中用户服务发出其事件,Wallet Service在消息传递总线上注册,侦听User Created事件并为用户创建Wallet。与此同时,如果用户使用电子钱包界面查看他的电子钱包,请检查用户是否刚刚创建并显示您的钱包创建正在进行中,请在一段时间内办理登机手续
答案 8 :(得分:1)
在微服务世界中,服务之间的通信应通过其余客户端或消息传递队列进行。根据您在服务之间进行通信的方式,可以通过两种方式处理跨服务的事务。我个人更喜欢消息驱动的体系结构,以便长事务对于用户而言应该是一种非阻塞的操作。 让我们以示例为例进行说明:
您必须注意的一件事是选择一个健壮的可靠消息主干,该主干可以在发生故障时保持状态不变。您可以使用kafka或Rabbitmq作为消息传递主干。由于最终的一致性,执行会有所延迟,但是可以通过套接字通知轻松地进行更新。通知服务/任务管理器框架可以是通过异步机制(如套接字)更新事务状态的服务,并且可以帮助UI更新显示适当的进度。
答案 9 :(得分:0)
最终的一致性是这里的关键。
指挥官负责分布式事务并控制。它知道要执行的指令,并将协调执行它们。在大多数情况下,只有两条指令,但是它可以处理多个指令。
指挥官负责保证所有指示的执行,这意味着退休。 当指挥官尝试进行远程更新而没有收到响应时,它不会重试。 这样,可以将系统配置为不易出现故障,并且可以自我修复。
当我们重试时,我们就具有幂等性。 幂等性是能够两次执行某项操作的属性,以使最终结果与只执行一次相同。 我们需要远程服务或数据源上的幂等,以便在多次接收到指令的情况下,仅处理一次。
最终的一致性 这解决了大多数分布式交易难题,但是在这里我们需要考虑几点。 每个失败的事务之后都会进行重试,重试的次数取决于上下文。
最终会出现一致性,即在重试期间系统处于不一致状态时,例如,如果客户已订购一本书,并进行了付款,然后更新了库存数量。如果库存更新操作失败,并且假定这是最后可用的库存,则在库存更新的重试操作成功之前,该书将仍然可用。重试成功后,您的系统将保持一致。
答案 10 :(得分:-2)
为什么不使用支持脚本/编程的API Management(APIM)平台?因此,您将能够在APIM中构建组合服务,而不会干扰微服务。我为此设计了使用APIGEE。