我所看到的在具有微服务的系统中发生的主要问题之一是,事务跨不同服务时的工作方式。在我们自己的体系结构中,我们一直在使用分布式事务来解决此问题,但是它们带有各自的问题。迄今为止,僵局尤其令人痛苦。
另一个选择似乎是某种定制的事务管理器,它知道您系统内的流程,并会为您处理回滚,因为它是跨整个系统的后台进程(因此它将告诉其他服务回滚)如果它们出现故障,请稍后通知他们)。
还有其他可接受的选择吗?两者似乎都有其缺点。第一个可能导致死锁和许多其他问题,第二个可能导致数据不一致。有更好的选择吗?
我所看到的在具有微服务的系统中发生的主要问题之一是,事务跨不同服务时的工作方式。在我们自己的体系结构中,我们一直在使用分布式事务来解决此问题,但是它们带有各自的问题。迄今为止,僵局尤其令人痛苦。
另一个选择似乎是某种定制的事务管理器,它知道您系统内的流程,并会为您处理回滚,因为它是跨整个系统的后台进程(因此它将告诉其他服务回滚)如果它们出现故障,请稍后通知他们)。
还有其他可接受的选择吗?两者似乎都有其缺点。第一个可能导致死锁和许多其他问题,第二个可能导致数据不一致。有更好的选择吗?
Answers:
通常的方法是尽可能地隔离那些微服务-将它们视为单个单元。然后,可以在整个服务上下文中开发事务(即,不属于常规数据库事务的一部分,尽管您仍然可以在服务内部拥有数据库事务)。
考虑事务如何发生以及什么样的事务对您的服务有意义,然后,您可以实施不执行原始操作的回滚机制,或者实施保留原始操作直到被告知真正提交的两阶段提交系统。当然,这两个系统都意味着您正在实现自己的系统,但是那时您已经在实现自己的微服务。
金融服务一直在做这种事情-如果我想将钱从我的银行转移到你的银行,就不会像数据库中那样有任何一笔交易。您不知道任何一家银行都在运行什么系统,因此必须像对待微服务一样有效地对待每个系统。在这种情况下,我的银行会将我的钱从我的帐户转移到持有帐户,然后告诉你的银行他们有一些钱,如果发送失败,我的银行将用他们尝试发送的钱退还我的帐户。
我认为标准的智慧是永远不要让交易跨越微服务边界。如果任何给定的数据集确实需要与另一个原子地保持一致,则这两个东西属于同一类。
这是在完全设计系统之前很难将系统拆分为服务的原因之一。在现代世界中,这可能意味着要写出来……
我认为,如果在您的应用程序中强烈要求一致性,那么您应该问自己,微服务是否是更好的方法。就像马丁·福勒( Martin Fowler)所说:
由于微服务在分散数据管理方面的出色表现,因此引入了最终的一致性问题。使用整体,您可以在单个事务中一起更新一堆东西。微服务需要多种资源来更新,并且分布式事务被拒绝(出于充分的理由)。因此,现在,开发人员需要意识到一致性问题,并弄清楚在执行任何使代码后悔的事情之前,如何检测不同步的情况。
但也许在您的情况下,您可以牺牲可用性的一致性
业务流程通常比您认为的更能容忍不一致的情况,因为业务通常更重视可用性。
但是,我也要问自己,微服务中是否存在用于分布式事务的策略,但是成本可能太高了。我想用马丁·福勒(Martin Fowler)一贯出色的文章和CAP定理给你我两分钱。
如本文至少一个答案中以及网络上其他地方所建议的,如果您需要在两个实体之间保持一致性,则可以设计一种微服务,将微实体在正常交易中持久保存在一起。
但是同时,您可能会遇到这样的情况,即实体确实不属于同一微服务,例如,销售记录和订单记录(当您订购某些东西以完成销售时)。在这种情况下,您很可能需要一种方法来确保两个微服务之间的一致性。
传统上已经使用了分布式事务,根据我的经验,它们可以很好地工作,直到它们扩展到一定程度,而锁定成为一个问题。您可以放宽锁定,以便实际上仅使用状态更改来“锁定”相关资源(例如,正在出售的商品),但这在这里开始变得棘手,因为您正在进入需要构建所有资源的区域自己执行此操作的逻辑,而不是说数据库为您处理。
我曾与那些已经建立自己的交易框架来处理此复杂问题的公司合作,但我不建议这样做,因为它价格昂贵且需要时间才能成熟。
有一些产品可以用螺栓固定在您的系统上,以确保一致性。业务流程引擎就是一个很好的例子,它们通常最终会通过使用补偿来处理一致性。其他产品也以类似的方式工作。通常,您最终会在客户端附近拥有一层软件,该层负责一致性和事务处理,并调用(微)服务来进行实际的业务处理。这样的产品之一就是可以与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。
无论您最终选择哪种解决方案,都必须认识到,系统中只有一小部分将要编写需要保持一致的内容-大多数访问可能是只读的,这一点很重要。因此,仅将事务管理构建到系统的相关部分中,以便仍然可以很好地扩展。
在微服务中,有三种方法可以实现差异之间的一致性。服务:
编排-一种跨服务管理事务和回滚的过程。
编排-服务之间相互传递消息,最终达到一致状态。
混合-混合以上两种。
要完整阅读,请访问以下链接:https : //medium.com/capital-one-developers/microservices-when-to-react-vs-orchestrate-c6b18308a14c
有许多解决方案的折衷范围超出了我所能接受的范围。当然,如果您的用例很复杂(例如在不同银行之间转移资金),则可能没有更舒适的选择。但是,让我们看一下在常见的情况下可以做些什么,在这种情况下,微服务的使用会干扰我们可能的数据库事务。
选项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,我们将其作为事务的一部分执行。
连接字符串是原始微服务提供给我们的,因此出于所有意图和目的,仍然认为该微服务执行了查询。我们只是通过客户端微服务物理地路由它。这有什么区别吗?好吧,它使我们可以将它与另一个查询放入同一事务中。
如果折衷方案是可以接受的,那么这种方法将为我们提供微服务体系结构中整体应用程序的直接事务性。
我将从分解问题空间开始- 确定您的服务边界。正确完成后,您将不需要跨服务进行事务处理。
不同的服务具有自己的数据,行为,动力,政府,业务规则等。一个好的开始是列出您的企业具有哪些高级功能。例如,营销,销售,会计,支持。另一个起点是组织结构,但要注意,有一些警告-由于某些原因(例如政治上的原因),它可能不是最佳的业务分解方案。更严格的方法是价值链分析。请记住,您的服务也可以包括人员,这并不是严格意义上的软件。服务应该通过事件相互通信。
下一步是雕刻这些服务。结果,您仍然获得了相对独立的聚合。它们代表一致性的单位。换句话说,它们的内部应该一致并且是ACID。聚合也可以通过事件相互通信。
如果您认为您的域首先需要一致性,请再考虑一遍。考虑到这一点,没有一个大型且关键任务系统可以构建。它们都是分布式的,最终是一致的。查看Pat Helland的经典论文。
这里是有关如何构建分布式系统的一些实用技巧。