我所看到的在具有微服务的系统中发生的主要问题之一是事务跨越不同服务时的工作方式。在我们自己的体系结构中,我们一直在使用分布式事务来解决此问题,但是它们带有各自的问题。到目前为止,尤其是僵局一直很痛苦。

另一种选择似乎是某种定制的事务管理器,它了解您系统内的流程,并会为您处理回滚。整个系统的后台进程(因此它将告诉其他服务回滚,如果它们关闭了,请稍后通知它们)。

还有另一个可接受的选项吗?两者似乎都有其缺点。第一个可能导致死锁和许多其他问题,第二个可能导致数据不一致。有更好的选择吗?

评论

可以肯定的是,那些微服务是同时被多个客户使用还是一次仅被一个客户使用?

我试图问这个与问题无关的问题,马塞尔。但是,让我们假设我们使用的系统足够大,可以同时完成这两个任务,并且我们希望拥有一种支持这两者的体系结构。

这是一个措辞不佳,但非常重要的问题。当大多数人想到“交易”时,他们几乎只考虑一致性,而不是事务的原子性,隔离性或持久性。实际上应该指出这个问题:“在给定微服务架构的情况下,如何创建符合ACID的系统。仅实现一致性而不是其余的ACID并没有真正的用处。除非您喜欢不良数据。

杰夫·菲舍尔(Jeff Fischer),您总是可以尝试改变我的问题,以使其措辞不那么“糟糕”。

这个问题和讨论与SO上的另一个问题密切相关。

#1 楼

通常的方法是尽可能地隔离那些微服务-将它们视为单个单元。然后,可以在整个服务的上下文中开发事务(即,不属于常规数据库事务的一部分,尽管您仍可以在服务内部拥有数据库事务)。

思考事务如何发生以及发生哪种类型然后,对于您的服务有意义的是,您可以实施不执行原始操作的回滚机制,或实施保留原始操作直到被告知真正提交的两阶段提交系统。当然,这两个系统都意味着您正在实现自己的服务,但随后您已经在实现自己的微服务。

金融服务一直在做这种事情-如果我想从我的账户中赚钱银行之间的交易,就不会像数据库中的交易一样。您不知道任何一家银行都在运行什么系统,因此必须像对待微服务一样有效地对待每个系统。在这种情况下,我的银行会将我的钱从我的帐户转移到持有帐户,然后告诉你的银行他们有一些钱,如果发送失败,我的银行将用他们尝试发送的钱退还我的帐户。

评论


您假设退款永远不会失败。

– Sanjeev Kumar Dangi
17年11月1日在11:11

@SanjeevKumarDangi的可能性较小,即使失败,也可以轻松处理,因为持有账户和实际账户在银行的控制之下。

– Galdin
17/12/27在10:29

有一些关联的元数据,但n种其他服务是一种处理方式。事件来源是另一种处理方式

– MrMesees
20年7月28日在9:27

#2 楼

我认为标准的智慧是永远不要让交易跨越微服务边界。如果任何给定的数据集确实需要与另一个原子地保持一致,那么这两件事就属于同一类。

,这是很难将系统拆分为服务的原因之一,除非您完全设计好它。在现代世界中,这可能意味着要写出来...

评论


最终,这种方法很可能导致将所有微服务合并到单个整体应用程序中。

– Slava Fomin II
17 Mar 23 '17 at 14:55

这是正确的方法。实际上很简单:如果您需要跨服务的事务,那么您的服务是错误的:重新设计它们! @SlavaFominII您所说的只有在不知道如何设计微服务系统的情况下才是正确的。如果您发现自己正在与微服务方法作斗争,请不要这样做,那么您的整体将比糟糕的微服务设计更好。仅当找到正确的服务边界时,才应拆分整体。否则,使用微服务不是正确的架构选择,它只是在大肆宣传。

– Francesc Castells
18-09-2在15:49



@FrancescCastells如果我们的服务确实需要与其他服务之间的交易,那意味着我们应该忽略有限的上下文,并以最终作为单个交易的方式对我们的服务进行建模?我是微服务领域的新手,如果还是天真的话,我仍然在阅读我的问题....:D:D

– Bilg Baggins
18年9月12日在9:31

@BilboBaggins我的意思是忽略有界上下文的相反。根据定义,交易是在有限的上下文中发生的,而不是跨多个上下文发生的。因此,如果您正确设计了微服务以及有限的上下文,那么您的事务就不应跨越多个服务。请注意,有时候,您需要的不是事务,而是在事情处理不正确时更好地处理最终的一致性和采取适当的补偿措施。

– Francesc Castells
18-09-17在20:14

@BilboBaggins对不起,我完全错过了您的评论!你想谈谈这个吗?您提出的问题是一个好问题。但是问题不是答案,而是问题本身。您说“会有两种不同的微服务”,并且您可能正在考虑“客户”和“帐户”微服务,这是错误的。您需要放弃entity = microservice的想法。这样一来,您就无需再在同一笔交易中存储客户名称和他的银行帐户,因为永远不会有依赖此名称的业务规则

– Francesc Castells
18-10-30在19:17

#3 楼

我认为,如果在您的应用程序中强烈要求一致性,那么您应该问自己,微服务是否是更好的方法。就像马丁·福勒(Martin Fowler)所说:微服务引入了最终的一致性问题,因为它们对分布式数据管理的坚持值得称赞。使用整体,您可以在单个事务中一起更新一堆东西。微服务需要多种资源来更新,并且分布式事务被拒绝(出于充分的理由)。因此,现在,开发人员需要意识到一致性问题,并弄清楚如何在什么事情不同步之前进行检测,然后再执行代码会让您后悔的事情。


但是对于您来说,您可能会牺牲可用性方面的一致性


业务流程通常比您认为的更能容忍不一致,因为业务经常会更重视可用性。


但是,我也要问自己,微服务中是否存在用于分布式事务的策略,但是成本可能太高了。
我想用马丁·福勒(Martin Fowler)一贯出色的文章和CAP定理给你我的两分钱。

评论


在分布式业务交易中,有时可以通过添加业务流程层或精心设计的队列等业务流程层来解决一致性问题。该层处理所有两个阶段的提交和回滚,并使微服务专注于特定的业务逻辑。但是回到CAP,对可用性和一致性的投资使性能成为牺牲品。微服务与OOP中构成您的业务的许多解耦类相比如何?

–格雷格
15年7月27日在14:02

您可以通过微服务使用RAFT来解决一致性FWIW

– f0ster
17年1月8日,下午2:34

+1。我经常想知道为什么在微服务环境中根本不需要事务,如果数据的所有“拉”视图都可以实现为物化视图。例如,如果一个微服务从一个帐户实施借项,而另一信用实施到另一帐户,则余额视图将仅考虑成对的信用和借项,而未匹配的信用和借项仍将存在于实现视图的缓冲区中。

–前哨
18年5月23日在13:09

#4 楼

如本文至少一个答案中以及网络上其他地方所建议的,如果您需要在两个实体之间保持一致性,则可以设计一种微服务,将微实体在正常交易中持久保存在一起。

但是同时,您可能会遇到这样的情况,即实体确实不属于同一微服务,例如,销售记录和订单记录(当您订购某些东西以完成销售时) 。在这种情况下,您很可能需要一种方法来确保两个微服务之间的一致性。

使用了传统的分布式事务,并且根据我的经验,它们可以很好地工作,直到扩展到一定程度,从而使锁定成为问题。您可以放宽锁定,以便实际上仅使用状态更改来“锁定”相关资源(例如,正在出售的商品),但这在这里开始变得棘手,因为您正在进入需要构建所有资源的区域自己做逻辑,而不是说数据库为您处理。

我与那些已经建立自己的交易框架来处理这个复杂问题的公司合作,但我不建议您这样做,因为它价格昂贵并且需要花费一些时间才能成熟。

有一些产品可以通过螺栓连接到您的系统上,以确保一致性。业务流程引擎就是一个很好的例子,它们通常最终会通过使用补偿来处理一致性。其他产品以类似的方式工作。通常,您最终会在客户端附近拥有一层软件,该层负责一致性和事务处理,并调用(微)服务来进行实际的业务处理。这样的产品之一就是可以与Java EE解决方案一起使用的通用JCA连接器(出于透明性:我是作者)。有关更多详细信息和此处提出的问题的更深入讨论,请参见http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html。

处理事务和一致性的另一种方法是将对微服务的调用包装为对诸如消息队列之类的事务的调用。以上面的销售记录/订单记录示例为例-您可以简单地让销售微服务将消息发送到订单系统,该订单系统在将销售写入数据库的同一事务中提交。结果是可很好扩展的异步解决方案。使用Web套接字等技术,您甚至可以解决阻塞问题,该问题通常与扩大异步解决方案有关。有关此类模式的更多想法,请参见我的另一篇文章:http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html。

采用哪种解决方案选择这一点很重要,要认识到,系统中只有一小部分将要编写需要保持一致的内容-大多数访问可能是只读的。因此,仅将事务管理构建到系统的相关部分中,以便它仍然可以很好地扩展。

评论


同时,我想说的是,应该认真考虑将流程转变为异步流程,其中流程的每个步骤都是完全事务性的。有关详细信息,请参见此处:blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html

–蚂蚁库茨切拉
18 Mar 10 '18 at 9:59

“从上面制作销售记录/订单记录示例-您可以简单地让销售微服务将消息发送到订单系统,该消息系统在将销售写入数据库的同一笔交易中提交。”不知道您的建议是否基本上是分布式事务,也就是说,在这种情况下您将如何处理回滚方案?例如。消息被提交到消息队列,但在数据库端回滚。

–仙人掌
19 Mar 8 '19 at 13:54

@sactiw不确定我是否考虑过两个阶段的提交,但是我现在避免这样做,而是写我的业务数据,并且需要在一次事务中将消息添加到队列中,这是我的微服务数据库。然后使用自动重试机制在事务提交后异步处理“事实”(也称为“命令”)。有关示例,请参阅2018-03-10的博客文章。尽可能避免回滚或补偿,而采用转发策略,因为它更容易实现。

–蚂蚁库茨切拉
19 Mar 9 '19 at 16:20

#5 楼

有许多解决方案的折衷范围超出了我所能接受的范围。当然,如果您的用例很复杂(例如在不同银行之间转移资金),则可能无法使用更舒适的选择。但是,让我们看一下在常见的情况下可以做些什么,在这种情况下,使用微服务会干扰我们可能的数据库事务。

方法1:尽可能避免使用事务

之前很明显并提到过,但如果可以管理的话,是理想的选择。这些组件是否实际上属于同一微服务?还是可以重新设计系统,使交易变得不必要?也许接受非事务性是最负担得起的牺牲。

方案2:使用队列

如果有足够的把握,其他服务将成功实现我们想要的一切可以,我们可以通过某种形式的队列来调用它。排队的项目要等到以后再取,但是我们可以确保该项目已排队。例如,假设我们要插入实体并发送电子邮件,例如一次交易。我们将电子邮件放在表中而不是调用邮件服务器。

Begin transaction
Insert entity
Insert e-mail
Commit transaction


一个明显的缺点是,多个微服务将需要访问同一表。 br />
方法3:在完成事务之前,最后要做外部工作

这种方法基于以下假设:提交事务不太可能失败。 br />
Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction


如果查询失败,则外部调用尚未发生。如果外部调用失败,则永远不会提交事务。

这种方法具有局限性,我们只能进行一个外部调用,并且必须在最后完成(即,我们不能在我们的系统中使用其结果)。查询)。

方法4:在挂起状态下创建事物

如此处所述,我们可以让多个微服务以不同的方式创建不同的组件,每个组件都处于挂起状态。

将执行任何验证,但不会在确定状态下创建任何内容。成功创建所有内容后,将激活每个组件。通常,此操作非常简单,出现问题的几率很小,以至于我们甚至更喜欢以非事务方式进行激活。

最大的缺点可能是我们必须考虑到待处理项目的存在。任何选择查询都需要考虑是否包括未决数据。大多数人应该忽略它。更新完全是另外一个故事了。然后让我们变得非传统。

取决于公司,这可能是不可接受的。我意识到。这是非正统的。如果不可接受,请选择其他路线。但是,如果这符合您的情况,它将简单而有力地解决问题。这可能是最可接受的折衷方法。

有一种方法可以将来自多个微服务的查询转换为简单的单个数据库事务。

返回查询,而不是执行

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction


从网络角度来说,每个微服务都必须能够访问每个数据库。请记住这一点,并考虑到将来的扩展。

如果事务中涉及的数据库位于同一服务器上,则这将是常规事务。如果它们在不同的服务器上,则将是分布式事务。无论如何,代码都是一样的。

我们收到查询,包括查询的连接类型,参数和连接字符串。我们可以将其包装在一个整洁的可执行Command类中,以保持流可读:微服务调用生成一个Command,我们将其作为事务的一部分执行。

连接字符串是原始微服务提供给我们的,因此出于所有意图和目的,该查询仍被视为由该微服务执行。我们只是通过客户端微服务物理地路由它。这有什么区别吗?好吧,它使我们可以将它与另一个查询放在同一事务中。

如果折衷是可以接受的,则这种方法为我们提供了微服务体系结构中整体应用程序的直接事务性。

评论


关于第3点:另一个缺点是,使用外部呼叫时,您可能不知道呼叫是否成功(例如,网络故障)。在这种情况下,您可能已经发送了电子邮件但不知道该电子邮件;如果回滚该交易,最终将收到一封针对未发生的交易发送的电子邮件。这就是为什么我朝着第2点前进的原因,在事务中提交了一条消息,该消息将由异步服务接收。这样,您可以将业务事务拆分为许多独立的数据库事务,同时确保最终将全部处理它们。

–本杰明
19/12/22在11:12

@Benjamin你是完全正确的。为了保证一致性,如今我也更喜欢这种方法。我的警告是“多个微服务将需要访问同一张表”是不正确的:微服务可以简单地在自己的表中排队,然后运行自己的后台服务来最终处理它们(执行幂等操作以确保处理)。

– Timo
19/12/23在12:25

#6 楼

在微服务中,有三种方法可以实现差异之间的一致性。服务:编排-一个跨服务管理事务和回滚的过程。
编排-服务在彼此之间传递消息并最终达到一致的状态。
混合-将以上两种混合使用。

要完整阅读,请访问以下链接:https://medium.com/capital-one-developers/microservices-when-to-react- vs-orchestrate-c6b18308a14c

#7 楼

我将从分解问题空间开始-确定您的服务边界。正确完成后,您将不需要跨服务进行事务。

不同的服务具有各自的数据,行为,动力,政府,业务规则等。一个好的开始是列出哪些企业拥有的高级功能。例如,营销,销售,会计,支持。另一个起点是组织结构,但要注意,有一些警告-由于某些原因(例如政治上的原因),它可能不是最佳的业务分解方案。更严格的方法是价值链分析。请记住,您的服务也可以包括人员,这并不是严格意义上的软件。这些服务应该通过事件相互通信。

下一步是开发这些服务。结果,您仍然获得相对独立的聚合。它们代表一致性的单位。换句话说,它们的内部应该一致并且是ACID。聚合可以通过事件相互通信。

如果您认为您的域首先需要一致性,请再考虑一下。考虑到这一点,没有一个大型且关键任务系统可以构建。它们都是分布式的,并最终保持一致。请查阅Pat Helland的经典文章。

这里有一些有关如何构建分布式系统的实用技巧。