跨微服务的数据一致性

时间:2017-05-13 08:13:05

标签: design-patterns akka microservices data-consistency

虽然每个微服务通常都有自己的数据 - 但某些实体需要在多个服务之间保持一致。

对于高度分布式环境(如微服务架构)中的此类数据一致性要求,设计有哪些选择?当然,我不想要共享数据库体系结构,其中单个数据库管理所有服务的状态。这违反了孤立和无共享的原则。

我明白,微服务可以在创建,更新或删除实体时发布事件。对此事件感兴趣的所有其他微服务可以相应地更新其各自数据库中的链接实体。

这是可行的,但它会导致整个服务中的许多细致和协调的编程工作。

Akka或任何其他框架可以解决这个用例吗?怎么样?

EDIT1:
为清楚起见,添加下图。
基本上,我试图理解,如果今天有可用的框架可以解决这个数据一致性问题。

对于队列,我可以使用任何AMQP软件,如RabbitMQ或Qpid等。 对于数据一致性框架,我不确定目前Akka或任何其他软件是否可以提供帮助。或者这种情况是如此罕见,以及如此反模式,以至于不需要任何框架? enter image description here

6 个答案:

答案 0 :(得分:7)

Microservices架构风格试图允许组织在开发和运行时让小团队拥有独立的服务。见read。最难的部分是以有用的方式定义服务边界。当您发现分割应用程序的方式导致需求经常影响多个服务时,会告诉您重新考虑服务边界。当您感到强烈需要在服务之间共享实体时,情况也是如此。

所以一般的建议是尽量避免这种情况。但是,有些情况下你无法避免这种情况。由于良好的架构通常是在做出正确的权衡,这里有一些想法。

  1. 考虑使用服务接口(API)表示依赖关系,而不是使用直接数据库依赖关系。这将允许每个服务团队根据需要更改其内部数据模式,并且只关注依赖性时的接口设计。这很有用,因为更容易添加其他API并慢慢弃用旧API而不是更改数据库设计以及所有相关的微服务(可能同时)。换句话说,只要仍然支持旧的API,您仍然可以独立部署新的Microsoft服务版本。这是亚马逊CTO推荐的方法,他开创了许多微服务方法。以下是与他一起推荐的interview in 2006

  2. 每当你真的无法避免使用相同的数据库并且你以多个团队/服务需要相同实体的方式分割你的服务边界时,你会在微服务团队和负责的团队之间引入两个依赖关系对于数据方案:a)数据格式,b)实际数据。这不是不可能解决的,而只是在组织中有一些开销。如果你引入太多这样的依赖关系,你的组织可能会在开发过程中陷入瘫痪和放缓。

  3. a)对数据方案的依赖。无需更改微服务即可修改实体数据格式。要解耦这一点,您必须严格版本化实体数据方案 ,并在数据库中支持微服务当前使用的所有数据版本。这将允许微服务团队自行决定何时更新其服务以支持新版本的数据方案。这对所有用例都不可行,但它适用于许多用途。

    b)对实际收集的数据的依赖性。已收集的数据和微服务的已知版本可以使用,但是当您有一些服务生成时会出现问题较新版本的数据和另一项服务取决于它 - 但尚未升级为能够阅读最新版本。这个问题很难解决,并且在许多情况下表明您没有正确选择服务边界。通常,您别无选择,只能在升级数据库中的数据的同时推出依赖于数据的所有服务。一种更古怪的方法是同时编写不同版本的数据(主要在数据不可变时起作用)。

    要解决a)和b)在其他一些情况下,依赖关系可以减少hidden data duplicationeventual consistency。这意味着每个服务都存储自己的数据版本,并且只有在该服务的需求发生变化时才会修改它。这些服务可以通过监听公共数据流来实现。在这种情况下,您将使用基于事件的体系结构,您可以在其中定义一组公共事件,这些事件可以由来自不同服务的侦听器排队并使用,这些服务将处理事件并存储与其相关的任何数据(可能会造成数据重复)。现在,一些其他事件可能表明内部存储的数据必须更新,并且每个服务都有责任使用自己的数据副本。维护此类公共事件队列的技术是Kafka

答案 1 :(得分:2)

理论限制

要记住的一个重要警告是CAP theorem

  

在存在分区的情况下,其中一个分区有两个选项:   一致性或可用性。选择一致性时   可用性,系统将返回错误或超时   因特殊信息无法保证是最新的   网络分区。

因此,通过“要求”某些实体在多个服务中保持一致,您增加了处理超时问题的可能性。

Akka分布式数据

Akka有一个distributed data module来共享群集中的信息:

  

所有数据条目都会传播到所有节点或具有特定节点的节点   角色,在集群中通过直接复制和基于八卦的方式   传播。您可以对一致性级别进行细粒度控制   用于读写。

答案 2 :(得分:2)

这里有同样的问题。我们拥有不同微服务中的数据,并且在某些情况下,一个服务需要知道另一微服务中是否存在特定实体。我们不希望这些服务彼此调用以完成一个请求,因为这会增加响应时间并缩短停机时间。而且,这增加了耦合深度的噩梦。客户也不应该决定业务逻辑和数据验证/一致性。我们也不希望像“ Saga Controllers”这样的中央服务提供服务之间的一致性。

因此,我们使用Kafka消息总线将“上游”服务中状态更改通知给观察服务。即使在错误情况下,我们也尽力不遗漏或忽略任何消息,并且我们使用Martin Fowler的“宽容阅读器”模式来尽可能松散地耦合。有时仍然会更改服务,并且在更改之后,它们可能需要以前可能已经在总线上发出的其他服务的信息,但是现在这些服务已经消失了(甚至Kafka也无法永远存储)。

我们现在决定将每个服务拆分为一个纯的,分离的Web服务(RESTful),该服务执行实际工作,并分离一个侦听总线并还可以调用其他服务的连接器服务。该连接器在后台运行。它仅由总线消息触发。然后它将尝试通过REST调用将数据添加到主服务。如果服务以一致性错误做出响应,则连接器将尝试通过从上游服务中获取所需数据并根据需要将其注入来修复此错误。 (我们无法承受批处理作业来“同步”整个数据块,因此我们只是获取所需的内容)。如果有更好的主意,我们总是开放的,但是“拉”或“改变数据模型”并不是我们认为可行的...

答案 3 :(得分:1)

我认为这里有两个主要的力量:

  • 解耦 - 这就是为什么你首先拥有微服务并且想要一种无共享的数据持久性方法
  • 一致性要求 - 如果我理解正确,您已经处于最佳一致状态

该图对我来说非常有意义,但我不知道任何框架是开箱即用的,可能是由于涉及许多特定于用例的权衡。我按如下方式处理问题:

正如您所示,上游服务会向消息总线发送事件。出于序列化的目的,我要仔细选择不会过多地与生产者和消费者联系的电线格式。我所知道的是protobuf和avro。如果它不关心新添加的字段,并且可以进行滚动升级,则可以在不需要更改下游的情况下向上游发展您的事件模型。

下游服务订阅事件 - 消息总线必须提供容错。我们正在使用kafka但是因为你选择了AMQP我假设它能满足你的需求。

如果网络出现故障(例如下游用户无法连接到代理),如果您赞成(最终)一致性而非可用性,您可以选择拒绝提供依赖于您知道的数据的请求,这些数据可能比某些预先配置的数据更为陈旧阈值。

答案 4 :(得分:0)

我认为您可以从两个角度,服务协作和数据建模来解决这个问题:

服务合作

您可以在此处选择服务编排和服务编排。您已经提到了服务之间的消息或事件交换。这将是一种编排方法,正如您所说的可能有效,但涉及在处理消息传递部分的每个服务中编写代码。我确定有这样的库。或者,您可以选择引入新组合服务的服务编排 - 协调器,它可以负责管理服务之间的数据更新。由于数据一致性管理现在被提取到一个单独的组件中,这将允许您在不触及下游服务的情况下在最终一致性和强一致性之间切换。

数据建模

您还可以选择重新设计参与微服务背后的数据模型,并将需要在多个服务中保持一致的实体提取到由专用关系微服务管理的关系中。这样的微服务有点类似于协调器,但耦合会减少,因为关系可以通用的方式建模。

答案 5 :(得分:-1)

"相应地更新各自数据库中的链接实体" - >数据复制 - > FAIL。

使用事件更新其他数据库与缓存相同,这会带来缓存一致性问题,这是您在问题中出现的问题。

保持本地数据库尽可能分开并使用pull语义而不是push,即在需要某些数据时进行RPC调用,并准备好优雅地处理可能的错误,如超时,丢失数据或服务不可用。 Akka或Finagle提供了足够的工具来做到这一点。

这种方式可能会影响性能,但至少可以选择交易内容和位置。减少延迟和提高吞吐量的可能方法是:

  • 扩展数据提供商服务,以便他们能够以更低的延迟处理更多的req / sec
  • 使用短到期时间的本地缓存。这将引入最终的一致性,但确实有助于提高绩效。
  • 直接使用分布式缓存和面部缓存一致性问题