跨微服务同步数据的正确方法是什么?


19

我对微服务架构比较陌生。我们有一个中等大小的Web应用程序,我在权衡将其细分为微服务而不是我们目前正在发展的单片系统的利弊。

据我了解,考虑微服务AB每个微服务都依赖于另一个服务。如果通过A说某件事已发生更改来发布消息,则B可以使用该消息并复制的信息的本地副本,A并使用该副本执行所需的任何B操作。

但是,如果B出现故障/失败,过一会儿又重新出现,该怎么办。在那段停机时间内,A又发布了两条消息。如何B知道如何更新其本地信息副本A

当然,如果BA队列的唯一使用者,那么一旦它重新联机,它就可以开始读取它,但是如果该队列还有其他使用者并且这些消息被消耗了怎么办?

作为一个更具体的示例,如果Users服务在Billing微服务关闭时更新了其电子邮件地址,如果Billing微服务又恢复了,它如何知道电子邮件已更新?

当微服务恢复正常运行时,是否会广播说“嘿,我已经备份了,给我您所有的当前信息?”

通常,什么是数据同步的最佳行业实践?


1
为了尽可能避免这种情况。
Telastyn

1
为什么Orders需要了解什么Users
kdgregory

这只是一个例子。用您想要的任何替换两个。
noblerare

扇形路由将解决您的“消息被其他人消耗”的问题。但还不清楚您要达到什么目标。
伊万

@Ewan我更新了我的原始帖子,以更好地解释我要问的问题。
noblerare

Answers:


5

我会挑战您“将数据推送到所有其他微服务”的整个想法。

通常,如果计费服务需要一个电子邮件地址,它只会向地址服务询问特定客户的电子邮件地址。它不需要保存所有地址数据的副本,也不需要通知是否有任何更改。它只是询问并从最新数据中获取答案。


我认为这个答案是完全正确的。它消除了许多与同步有关的问题。实际上,我现在正在查看存在此类问题的代码,因为不同的服务正在保留信息副本并存在此类同步问题。
DaveG '18年

2
感谢您的回答。那么为什么需要发布/订阅模型和消息队列呢?如果我们试图“拉”而不是“推”数据,则我们担心服务延迟。
noblerare

AFAIK,如果某些更改(例如在pub / sub中),您的服务不需要立即做出反应,但是偶尔需要数据。那我就拉它。如果您担心延迟,则可以缓存数据,但这又以不知道数据是否最新为代价。如果文件很大,您还可以在再次拉东西之前询问是否有任何更改。
J. Fabian Meier

请记住,此解决方案的代价是紧密耦合依赖服务,这意味着当用户服务不可用时,电子邮件地址将不可用。中断服务的最初想法之一就是使它们独立部署,可伸缩等。如果所有服务之间相互直接通信而没有缓存或保证高可用性,那么当一个系统宕机时,它们都会下去。
dukethrash

@dukethrash然后使其高度可用。
法比·迈耶

5

在进行了更多研究之后,我偶然发现了这篇文章,从中我引出了一些我认为对我想完成的事情(以及将来的读者)有用的引用。这提供了一种在命令式编程模型上采用反应式编程模型的方法。

活动采购

这里的想法是以不可变事件的形式表示每个应用程序的状态转换。然后,事件在发生时以日志或日志形式存储(也称为“事件存储”)。还可以无限期地查询和存储它们,以表示整个应用程序状态随时间变化的方式。

这有助于完成的工作是,如果微服务出现故障,但与之相关的其他事件正在发布,并且事件被该微服务的其他实例消耗,则当该微服务恢复时,它可以引用此消息event store以检索所有它在下降期间错过的事件。

Apache Kafka作为事件代理

考虑使用Apache Kafka,它可以每秒存储和调度数千个事件,并具有内置的复制和容错机制。它具有事件的持久性存储,可以将事件无限期地存储在磁盘上,并且可以随时将主题(卡夫卡的幻想队列)中的事件消耗(但不能删除)。

然后为事件分配偏移量,以在主题内唯一地标识它们-Kafka可以管理偏移量本身,轻松提供“最多一次”或“至少一次”的交付语义,但是当事件使用者加入主题时也可以协商它们,从而允许微服务从任意时间(通常是从消费者离开的地方)开始消费事件。如果在用例“成功完成”时最后消耗的事件偏移量在事务上持久地保留在服务的本地存储中,则可以轻松地使用该偏移量来实现“恰好一次”的事件传递语义。

实际上,当消费者向Kafka证明自己的身份时,Kafka会记录向哪个消费者传递了哪些消息,以便不再为它服务。

萨加斯

对于确实需要在不同服务之间进行通信的更复杂的用例,必须很好地认识到完成用例的责任-用例是分散的,并且仅在所有涉及的服务都确认其任务成功完成后才完成,否则整个用例都必须失败并且必须触发纠正措施以回滚任何无效的本地状态。

这是传奇开始发挥作用的时候。传奇是一系列本地交易。每个本地事务都会更新数据库并发布消息或事件以触发传奇中的下一个本地事务。如果本地事务因为违反业务规则而失败,那么该传奇将执行一系列补偿事务,以撤消先前的本地事务所做的更改。阅读以获得更多信息。


我仍然不明白为什么要构建如此复杂的结构。如果每个服务仅保留自己的数据并根据请求将其提供给其他服务,通常会容易得多。
J. Fabian Meier

^但是会降低系统的可用性。如果需要高弹性,则可能需要复杂的结构。
avmohan

1

即使我迟到了,我也要花两分钱,因为我认为当您要评估设计事件驱动型微服务架构时,这一点很重要。每个微服务都准确知道哪些事件会影响其状态,并能够等待它们。当微服务不可用时,应该有一个组件来保存发生故障的微服务所需的消息,直到无法“消费”它们为止。实际上,这是“生产者/消费者”模型,而不是“发布/订阅”模型。消息代理(例如Kafka,RabbitMQ,ActiveMQ等)通常是实现此行为的最佳方法(除非您没有实现诸如事件源之类的其他功能),并提供持久队列和ack / nack机制。

现在,微服务知道一条消息最终会被传递,但还远远不够:期望单个消息传递的方式是什么?它可以管理同一事件通知的多个副本的传送吗?这是传递语义的问题(至少一次,恰好一次)

最后的想法):

  1. 将微服务添加到需要消耗其他事件的架构时,您必须进行第一次同步

  2. 甚至经纪人也可能失败,在这种情况下,消息会丢失

对于这两种情况,使用简单的机制为您的微服务状态重新补充水分将很有用。它可能是REST API或发送消息的脚本,但最重要的是要具有执行某些维护任务的方法


0

您可以使用发布/订阅模式,即更换一个正常的事件队列A服务发表的主题的新的消息ŧB输入微服务会订阅同一主题。

理想情况下,B它将是无状态服务,并且它将利用分离的持久性服务,这样,将失败的B服务实例替换为产生一个或多个B服务实例以继续其工作,并从同一共享的持久性服务读取内容。


0

如果消息是由A发布的,表明发生了某些更改,则B可以使用该消息并复制A的信息的本地副本,然后使用该信息来执行B所需做的任何事情。

如果您希望B能够访问A的内部数据,则最好让它访问A的内部数据库。

但是,您不应该这样做,因为面向服务的体系结构的全部要点是服务B无法看到服务A的内部状态,并且只能通过REST API发出请求(反之亦然)。

在您的情况下,您可以拥有一个用户数据服务,该服务负责存储所有用户数据。希望使用该数据的其他服务仅在需要时才请求它,而不会保留本地副本(顺便说一句,如果您考虑GDPR合规性就非常有用)。用户数据服务可以支持简单的CRUD操作,例如“创建新用户”或“更改user_id 23的名称”,也可以具有更复杂的操作,“查找所有标准用户,并在接下来的两周内生日”高级试用状态”。现在,当您的结算服务需要向用户42发送电子邮件时,它将询问用户数据服务“ user_id 42的电子邮件地址是什么”,将其内部数据与所有结算信息一起使用以制作电子邮件,然后可以通过电子邮件地址和正文到邮件服务器。

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.