假设我们有一个用户、钱包REST微服务和一个API网关,它将这些东西粘合在一起。当Bob在我们的网站上注册时,我们的API网关需要通过user微服务创建一个用户,并通过wallet微服务创建一个钱包。

下面是一些可能出错的情况:

用户Bob创建失败:没关系,我们只是向Bob返回一个错误消息。我们使用SQL事务,所以没有人在系统中看到Bob。一切都很好:) 用户Bob已经创建,但是在我们的钱包创建之前,我们的API网关硬崩溃了。我们现在有一个没有钱包的用户(数据不一致)。 创建了用户Bob,当我们创建钱包时,HTTP连接断开。钱包的创建可能成功,也可能失败。

有什么解决方案可以防止这种数据不一致的发生?是否存在允许事务跨越多个REST请求的模式?我读过维基百科上关于两阶段提交的页面,它似乎涉及到这个问题,但我不确定如何在实践中应用它。这篇原子分布式事务:一篇RESTful设计论文看起来也很有趣,尽管我还没有读过。

或者,我知道REST可能不适合这个用例。处理这种情况的正确方法可能是完全放弃REST并使用不同的通信协议,如消息队列系统?或者我应该在我的应用程序代码中强制一致性(例如,通过有一个后台作业来检测不一致并修复它们,或者通过在我的用户模型上有一个“state”属性与“creating”,“created”值等)?


当前回答

恕我直言,微服务架构的一个关键方面是事务被限制在单个微服务中(单一责任原则)。

在当前示例中,User创建将是一个自己的事务。用户创建将把USER_CREATED事件推入事件队列。钱包服务将订阅USER_CREATED事件并创建钱包。

其他回答

所有分布式系统都存在事务一致性问题。最好的方法是像你说的那样,进行两阶段提交。在挂起状态下创建钱包和用户。创建该用户后,进行单独调用以激活该用户。

最后一个调用应该是安全可重复的(以防连接断开)。

这将需要最后一次调用知道两个表(这样就可以在单个JDBC事务中完成)。

或者,您可能想要考虑一下为什么您如此担心没有钱包的用户。你认为这会引起问题吗?如果是这样,也许将它们作为单独的rest调用是一个坏主意。如果用户没有钱包就不应该存在,那么您可能应该将钱包添加到用户中(在最初的POST调用中创建用户)。

如果我的钱包只是用户在同一个sql数据库中的另一组记录,那么我可能会将用户和钱包的创建代码放在同一个服务中,并使用正常的数据库事务设施进行处理。

在我看来,你是在问,当钱包创建代码要求你接触另一个或多个系统时会发生什么?我认为这完全取决于创建过程的复杂性和风险。

如果只是涉及到另一个可靠的数据存储(比如一个不能参与sql事务的数据存储),那么根据整个系统参数,我可能愿意冒第二次写入不会发生的小概率风险。我可能什么也不做,只是引发一个异常,并通过补偿事务或甚至一些特别方法处理不一致的数据。就像我总是告诉开发者的那样:“如果这类事情发生在应用中,它不会被忽视。”

随着钱包创建的复杂性和风险的增加,您必须采取措施来改善所涉及的风险。假设有些步骤需要调用多个合作伙伴api。

此时,您可能会引入消息队列以及部分构造的用户和/或钱包的概念。

确保实体最终被正确构造的一个简单而有效的策略是让作业重试,直到它们成功为止,但这在很大程度上取决于应用程序的用例。

我也会仔细思考为什么我的配置过程中有一个容易失败的步骤。

最终的一致性是这里的关键。

选择其中一个服务作为事件的主要处理程序。 该服务将处理单个提交的原始事件。 主处理程序将负责将次要效果异步地传递给其他服务。 主处理程序将对其他服务调用进行编排。

指挥官负责分布式事务并进行控制。它知道要执行的指令,并协调执行它们。在大多数情况下,只有两条指令,但它可以处理多条指令。

指挥官负责保证所有指令的执行,这意味着退休。 当指挥官试图执行远程更新而没有得到响应时,它没有重试。 通过这种方式,系统可以配置为更不容易出现故障,并自我修复。

当我们进行重试时,我们有幂等性。 幂等性是这样一种性质,即可以做某事两次,最终结果与只做一次一样。 我们需要远程服务或数据源上的幂等性,以便在它多次接收指令的情况下,它只处理一次指令。

最终一致性 这解决了大多数分布式事务的挑战,但是我们需要考虑以下几点。 每一个失败的事务都将被重试,尝试重试的数量取决于上下文。

一致性是最终的,即在重试期间系统处于不一致的状态,例如,如果客户订购了一本书,并支付了费用,然后更新了库存数量。如果库存更新操作失败,并且假设这是最后一个可用的库存,那么在库存更新的重试操作成功之前,这本书仍然可用。重试成功后,您的系统将保持一致。

恕我直言,微服务架构的一个关键方面是事务被限制在单个微服务中(单一责任原则)。

在当前示例中,User创建将是一个自己的事务。用户创建将把USER_CREATED事件推入事件队列。钱包服务将订阅USER_CREATED事件并创建钱包。

在微服务世界中,服务之间的通信应该通过rest客户端或消息队列。有两种方法可以跨服务处理事务,具体取决于服务之间的通信方式。我个人更喜欢消息驱动的体系结构,这样长时间的事务对用户来说应该是一个无阻塞的操作。 让我们举个例子来解释一下:

使用事件Create user创建用户BOB,并将消息推送到消息总线。 订阅此事件的钱包服务可以创建用户对应的钱包。

The one thing which you have to take care is to select a robust reliable message backbone which can persists the state in case of failure. You can use kafka or rabbitmq for messaging backbone. There will be a delay in execution because of eventual consistency but that can be easily updated through socket notification. A notifications service/task manager framework can be a service which update the state of the transactions through asynchronous mechanism like sockets and can help UI to update show the proper progress.