虽然每个微服务通常都有自己的数据,但某些实体需要在多个服务之间保持一致。
对于高度分布式环境(如微服务体系结构)中的数据一致性要求,有哪些设计选择呢?当然,我不希望共享数据库体系结构,在这种体系结构中,单个DB管理所有服务的状态。这违反了隔离和无共享原则。
我知道,当一个实体被创建、更新或删除时,一个微服务可以发布一个事件。所有其他对该事件感兴趣的微服务可以相应地更新它们各自数据库中的链接实体。
这是可行的,但是它会导致跨服务的大量仔细和协调的编程工作。
Akka或任何其他框架能解决这个用例吗?如何解决?
编辑1:
为清楚起见,添加了下图。
基本上,我试图了解,如果有可用的框架,今天可以解决这个数据一致性问题。
对于队列,我可以使用任何AMQP软件,如RabbitMQ或Qpid等。对于数据一致性框架,我不确定目前Akka或任何其他软件是否可以提供帮助。或者,这种情况是如此罕见,并且是如此的反模式,以至于不需要任何框架?
7条答案
按热度按时间cl25kdpy1#
同样的问题。我们在不同的微服务中有数据,有时候一个服务需要知道另一个微服务中是否有特定的实体。我们不希望服务互相调用来完成一个请求,因为这会增加响应时间和停机时间。同时也会增加耦合深度的噩梦。客户端也不应该决定业务逻辑和数据验证/一致性。我们也不希望像“ Saga Controllers”这样的中央服务提供服务之间的一致性。
因此,我们使用Kafka消息总线来通知观察服务“上游”服务的状态变化,我们非常努力地不错过或忽略任何消息,即使在错误的情况下,我们也使用Martin Fowler的“宽容的读取器”模式来尽可能松散地耦合。有时服务会发生更改,更改后,它们可能需要来自其他服务的信息,这些信息可能是它们之前在总线上发出的,但它们现在消失了(即使Kafka也不能永远储存)。
我们现在决定将每个服务拆分为一个纯的、分离的Web服务(RESTful)和一个单独的Connector-Service,Connector-Service监听总线,也可以调用其他服务。此连接器在后台运行。它仅由总线消息触发。然后,它将尝试通过REST调用向主服务添加数据。如果服务以一致性错误响应,连接器将尝试通过从上游服务获取所需的数据并根据需要注入数据来修复此问题。(我们无法承担批处理作业来“同步”整个数据块,因此我们只获取所需的数据)。如果有更好的想法,我们始终是开放的,但“拉取”或“仅更改数据模型”并不是我们认为可行的...
xqk2d5yq2#
我认为您可以从两个Angular 来处理这个问题,即服务协作和数据建模:
服务协作
在这里,您可以在服务编排和服务编排之间进行选择。您已经提到了服务之间的消息或事件交换。这将是编排方法,正如您所说,它可能会起作用,但涉及到在每个服务中编写处理消息传递部分的代码。不过,我确信有相应的库。或者,您可以选择服务编排,在其中引入新的组合服务-编排器。该组件可以负责管理服务之间的数据更新,因为数据一致性管理现在被提取到一个单独的组件中,所以这将允许您在最终一致性和强一致性之间切换,而无需接触下游服务。
数据建模
您还可以选择重新设计参与微服务背后的数据模型,并将需要在多个服务之间保持一致的实体提取到由专用关系微服务管理的关系中。这样的微服务在某种程度上类似于编排器,但耦合会减少,因为可以用通用的方式对关系进行建模。
pcrecxhr3#
我认为有两种主要力量在起作用:
这个图对我来说非常有意义,但是我不知道有什么框架可以开箱即用,这可能是因为涉及到许多用例特定的权衡。
上游服务将事件发送到消息总线上,正如您所展示的。为了序列化,我会仔细选择不会将生产者和消费者耦合太多的线格式。我知道的是protobuf和avro。如果它不关心新添加的字段,您可以在不改变下游的情况下向上游发展您的事件模型,如果它关心新添加的字段,则可以进行滚动升级。
下游服务订阅事件-消息总线必须提供容错。我们使用Kafka来实现这一点,但是既然你选择了AMQP,我就假设它能提供你所需要的。
如果出现网络故障(例如,下游使用者无法连接到代理),如果您倾向于(最终)一致性而不是可用性,则可以选择拒绝服务依赖于您知道可能比某个预配置阈值更陈旧的数据的请求。
7gyucuyw4#
“相应地更新其各自数据库中的链接实体”-〉数据复制-〉失败。
使用事件来更新其他数据库与缓存相同,这会带来缓存一致性问题,也就是您在问题中提到的问题。
保持本地数据库尽可能的分离,使用pull语义而不是push,也就是说,当你需要一些数据时,进行RPC调用,并准备好优雅地处理可能的错误,比如超时,丢失数据或服务不可用。Akka或Finagle提供了足够的工具来做正确的事情。
这种方法 * 可能 * 会损害性能,但至少你可以选择交易的内容和地点。减少延迟和增加吞吐量的可能方法有:
v09wglhw5#
管理模块之间的数据访问
什么是模块?
模块是一个软件,它本身具有某种功能。一个模块可以作为一个整体与其他模块一起部署,也可以作为一个微服务单独部署。当定义模块时,应该小心,因为管理模块之间的数据访问变得更加困难。因此,它需要在特定领域的大量经验来决定。最好犯合并“实际的两个模块”的错误。因为如果你把一个模块分成两个,而你不应该把它分成两个,那么在这两个模块之间将会有很多的数据访问,这可能很难管理,尤其是在有事务逻辑的情况下。但有时当事情开始变大时,有必要使模块变得非常合适。下面是一个决策树,我用它来决定我必须选择哪种策略:
数据读取的决策树
如果有两个服务,使得A依赖于B...
数据写入的决策树
如果有两个服务,使得A依赖于B...
复合数据读取1:批处理、连接后排序/过滤、事务管理等。复杂数据写入2:IO密集、CPU密集、网络密集
q8l4jmvw6#
微服务体系结构风格试图允许组织在开发和运行时让小团队拥有独立的服务。参见read。最难的部分是以一种有用的方式定义服务边界。当你发现你分割应用程序的方式导致需求频繁地影响多个服务时,你会重新考虑服务边界。当您感到强烈需要在服务之间共享实体时,也是如此。
因此,一般的建议是尽量避免这种情况。然而,可能有些情况下,你无法避免这种情况。因为一个好的架构往往是关于作出正确的权衡,这里有一些想法。
1.请考虑使用服务接口来表示依赖关系(API)而不是直接的DB依赖关系。这将允许每个服务团队根据需要更改其内部数据模式,并且在涉及依赖关系时只考虑接口设计。这是很有帮助的,因为添加额外的API并慢慢淘汰旧的API比更改DB设计沿着所有依赖的微服务更容易(可能同时)。换句话说,您仍然可以独立部署新的微服务版本,只要旧的API仍然受支持。这是亚马逊首席技术官推荐的方法,他是许多微服务方法的先驱。下面是推荐阅读的一个interview in 2006。
1.每当您确实无法避免使用相同的数据库,并且您以多个团队/服务需要相同实体的方式分割服务边界时,您将在微服务团队和负责数据方案的团队之间引入两个依赖项:a)数据格式,B)实际数据。这不是不可能解决的问题,但只会给组织带来一些开销。如果引入太多这样的依赖项,您的组织很可能会在开发过程中受到削弱并减慢速度。
a)对数据方案的依赖性。如果不要求在微服务中进行更改,则无法修改实体数据格式。要将其分离,您必须 * 严格 * 版本化实体数据方案,并在数据库中支持微服务当前正在使用的所有数据版本。这将允许微服务团队自己决定何时更新他们的服务以支持新版本的数据方案,但它适用于许多情况。
**B)对实际收集的数据的依赖性。**已经收集的并且是微服务的已知版本的数据可以使用,但当您有一些服务生成较新版本的数据,而另一个服务依赖于该数据,但尚未升级到能够读取最新版本时,就会出现此问题。此问题很难解决,在许多情况下,这表明您没有正确选择服务边界。通常,您别无选择,只能在升级数据库中的数据的同时推出依赖于该数据的所有服务。一种更古怪的方法是并发地写入不同版本的数据(这种方法主要适用于数据不可变的情况)。
为了解决a)和b)的问题,在其他一些情况下,可以通过隐藏数据复制和最终的一致性来减少依赖性。这意味着每个服务存储其自己的数据版本,并且仅在该服务的需求改变时才修改它。服务可以通过监听公共数据流来做到这一点。在这种情况下,您将使用基于事件的体系结构,在该体系结构中,您定义了一组公共事件,这些事件可以排队,并由来自不同服务的侦听器使用,这些服务将处理事件并存储现在,一些其他事件可能指示必须更新内部存储的数据,并且每个服务都有责任使用其自己的数据副本来执行此操作。用于维护此类公共事件队列的技术是Kafka。
wpcxdonn7#
理论限制
需要记住的一个重要警告是CAP theorem:
在存在分区的情况下,则剩下两个选项:一致性或可用性。当选择一致性而不是可用性时,如果由于网络分区而无法保证特定信息是最新的,则系统将返回错误或超时。
因此,通过“要求”某些实体在多个服务中保持一致,您将增加必须处理超时问题的可能性。
Akka分布式数据
Akka有一个distributed data module来共享集群内的信息:
所有数据条目通过直接复制和基于流言的传播传播传播到集群中的所有节点或具有特定角色的节点。您可以对读取和写入的一致性级别进行细粒度控制。