微服务最被接受的交易策略是什么


80

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

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

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


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

2
我试图问这个与问题无关的问题,马塞尔。但是,让我们假设我们使用的系统足够大,可以同时完成这两项工作,并且我们希望拥有一种能够同时支持这两项的体系结构。
克里斯托夫

4
这是一个措辞不佳,但非常重要的问题。当大多数人想到“交易”时,他们几乎只考虑一致性,而不是事务的原子性,隔离性或持久性。这个问题实际上应该说“你怎么创建给予了微服务架构的ACID兼容的系统,只有实现一致性和不耐酸的其余部分是不是真的有用,除非你喜欢坏数据。
杰夫·费希尔

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

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

Answers:


42

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

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

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


5
您假设退款永远不会失败。
Sanjeev Kumar Dangi

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

30

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

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


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

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

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

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

我不明白你的意思,我们有机会在聊天中讨论这个问题吗?
Bilbo Baggins

17

我认为,如果在您的应用程序中强烈要求一致性,那么您应该问自己,微服务是否是更好的方法。就像马丁·福勒 Martin Fowler)所说

由于微服务在分散数据管理方面的出色表现,因此引入了最终的一致性问题。使用整体,您可以在单个事务中一起更新一堆东西。微服务需要多种资源来更新,并且分布式事务被拒绝(出于充分的理由)。因此,现在,开发人员需要意识到一致性问题,并弄清楚在执行任何使代码后悔的事情之前,如何检测不同步的情况。

但也许在您的情况下,您可以牺牲可用性的一致性

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

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


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

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

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

16

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

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

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

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

有一些产品可以用螺栓固定在您的系统上,以确保一致性。业务流程引擎就是一个很好的例子,它们通常最终会通过使用补偿来处理一致性。其他产品也以类似的方式工作。通常,您最终会在客户端附近拥有一层软件,该层负责一致性和事务处理,并调用(微)服务来进行实际的业务处理。这样的产品之一就是可以与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
Ant Kutschera

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

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


1

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

选项1:尽可能避免交易

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

选项2:使用队列

如果有足够的把握可以确保其他服务能够成功完成我们想做的任何事情,我们可以通过某种形式的队列来调用它。排队的物品要等到以后再取,但是我们可以确保该物品已排队

例如,假设我们要插入一个实体并发送电子邮件作为单个交易。我们将电子邮件放在表中而不是调用邮件服务器。

Begin transaction
Insert entity
Insert e-mail
Commit transaction

一个明显的缺点是多个微服务将需要访问同一表。

选项3:在完成交易之前,最后进行外部工作

该方法基于以下假设:提交事务极不可能失败。

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

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

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

选项4:在挂起状态下创建事物

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

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

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

选项5:让微服务共享其查询

没有其他选择适合您吗?然后让我们变得非传统

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

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

返回查询,而不是执行查询。

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

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

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

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

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

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


0

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

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.