播种微服务数据库

时间:2020-02-28 12:38:48

标签: microservices event-sourcing

给出控制模型(产品,假设其具有ID,标题,价格的唯一字段)的服务A(CMS),以及必须显示给定模型的服务B(运费)和C(电子邮件)方法是在事件采购方法中跨这些服务同步给定模型信息吗?假设产品目录很少发生更改(但确实发生了更改),并且有一些管理员可以非常频繁地访问货运和电子邮件数据(示例功能为:B:display titles of products the order contained和C:{ {1}})。每个服务都有自己的数据库。

解决方案1 ​​

在事件内发送有关产品的所有必需信息-这意味着display content of email about shipping that is going to be sent的结构如下:

order_placed

关于服务B和C的产品信息存储在{ order_id: [guid], product: { id: [guid], title: 'Foo', price: 1000 } } 表的product JSON属性中

如此,为了显示必要的信息,仅使用从事件中检索到的数据

问题:根据需要在B和C中显示的其他信息,事件中的数据量可能会增加。 B和C可能不需要有关Product的相同信息,但是事件必须包含两者(除非我们将事件分成两个)。如果给定事件中不存在给定数据,则代码将无法使用它-如果我们向给定产品添加颜色选项,则对于B和C中的现有订单,给定产品将是无色的,除非我们更新事件,然后重新运行它们。

解决方案2

在事件内仅发送产品的向导-这表示orders的以下结构:

order_placed

关于服务B和C的产品信息存储在{ order_id: [guid], product_id: [guid] } 表的product_id属性中

在需要时,服务B和C会通过对orders端点执行API调用来检索产品信息

问题:这使得B和C始终依赖于A。如果产品架构在A上发生更改,则必须(突然)对依赖于它们的所有服务进行更改

解决方案3

在事件内仅发送产品的Guid-这表示order_placed的结构如下:

A/product/[guid]

关于服务B和C的产品信息存储在{ order_id: [guid], product_id: [guid] } 表中; products表上仍然有product_id,但是在A,B和C之间复制了orders数据; B和C可能包含与A有关的关于Product的不同信息

创建产品B和C时将植入产品信息,并通过调用products端点(显示所有产品的必需信息)或执行对A的直接DB访问来更改有关产品的信息,从而更新产品信息并复制给定服务所需的必要产品信息。

问题:这使得B和C依赖于A(播种时)。如果产品模式在A上发生更改,则必须对所有依赖于它们的服务(在播种时)进行更改


根据我的理解,正确的方法是采用解决方案1,并且按照某种逻辑更新事件历史记录(如果产品目录未更改,并且我们希望添加要显示的颜色,则可以安全地将历史记录更新为获取产品的当前状态并填充事件中缺少的数据)或满足给定数据的不存在(如果产品目录已更改,并且我们想添加要显示的颜色,则无法确定是否在该时间点过去给定的产品是否有颜色-我们可以假定先前目录中的所有产品都是黑色的,并且可以通过更新事件或代码来满足要求。

4 个答案:

答案 0 :(得分:3)

解决方案3确实接近正确的想法。

一种思考方式:B和C分别是缓存所需数据的“本地”副本。在B(同样在C)处理的消息使用本地缓存的信息。同样,使用本地缓存的信息生成报告。

数据通过稳定的API从源复制到缓存。 B和C甚至不需要使用相同的API-它们使用适合其需要的任何提取协议。实际上,我们定义了一个契约-协议和消息模式-约束了提供者和消费者。然后,该合同的任何消费者都可以连接到任何供应商。向后不兼容的更改需要签订新合同。

服务根据其需求选择适当的缓存失效策略。这可能意味着要有规律地从源中拉出更改,或者响应于通知事情可能已经更改,甚至“按需”通知-充当通读缓存,并在发生时退回到存储的数据副本源不可用。

从某种意义上说,这给了您“自治权”,因为当A暂时不可用时,B和C可以继续提供业务价值。

推荐阅读:Data on the Outside, Data on the Inside,Pat Helland 2005。

答案 1 :(得分:2)

There are two hard things在计算机科学中,其中之一是缓存失效。

解决方案2绝对是我的默认位置,并且通常只在遇到以下情况之一时才考虑实施缓存:

  1. 对服务A的API调用导致性能问题。
  2. 服务A宕机和无法检索数据的成本对企业而言是重大的。

性能问题实际上是主要的驱动因素。解决#2的方法很多,不涉及缓存,例如确保Service A高度可用。

缓存增加了系统的复杂性,并且可能创建难以推理的极端情况以及很难复制的错误。您还必须减轻在存在较新数据时提供陈旧数据的风险,从业务角度来看,这 可能比(例如)显示“服务A已关闭的消息-请尝试再过一遍。”

摘自Udi Dahan的this excellent article

这些依赖关系慢慢蔓延到您身上,绑上鞋带 在一起,逐渐放慢发展速度,破坏 您的代码库的稳定性,其中更改了系统的一部分 打破其他部分。一千割,这是一个缓慢的死亡, 结果没人能确切确定我们做出的重大决定 一切都变得如此糟糕。

此外,如果您需要对产品数据进行时间点查询,则应采用将数据存储在产品数据库中的方式(例如开始/结束日期)进行处理,并应在API中明确公开(有效日期必须是用于查询数据的API调用的输入)。

答案 2 :(得分:2)

很难简单地说一种解决方案比另一种更好。在解决方案2和解决方案3中选择一种取决于其他因素(缓存持续时间,一致性容限...)

我的2美分:

缓存失效可能很难,但是问题陈述中提到产品目录很少更改。这一事实使产品数据成为缓存的理想选择

解决方案1(NOK)

  • 数据在多个系统中重复

解决方案2(确定)

  • 提供强大的一致性
  • 仅在产品服务高度可用并提供良好性能时有效
  • 如果电子邮件服务准备了摘要(包含许多产品),则总响应时间可能会更长

解决方案3(复杂但首选)

  • 首选API方法,而不是直接访问数据库来检索产品信息
  • 弹性服务-当产品服务中断时不会受到影响
  • 消费应用程序(运输和电子邮件服务)在事件发布后立即检索产品详细信息。在这几毫秒内产品服务下降的可能性很小。

答案 3 :(得分:1)

一般而言,由于这两个服务之间的时间耦合(除非这些服务之间的通信非常稳定且不会非常频繁),所以我强烈建议您不选择选项2。时间耦合就是您所说的this makes B and C dependant upon A (at all times),它意味着如果A向下或无法从B或C到达,则B和C无法实现其功能。

我个人认为选项1和3都存在有效选项的情况。

如果A与B&C之间的通信量很高,或者事件所需的数据量足够大而令人担忧,则选项3是最佳选择,因为网络负担很大降低得多,并且操作等待时间将随着消息大小的减少而减少。这里要考虑的其他问题是:

  1. 合同的稳定性:如果离开A的消息的合同经常更改,则在消息中放置很多属性将导致使用者发生很多变化。但是,在这种情况下,我认为这不是什么大问题,因为:
    1. 您提到系统A是CMS。这意味着您正在一个稳定的域上工作,因此,我认为您不会看到频繁的更改
    2. 由于B和C都在运送和通过电子邮件发送邮件,并且您正在从A接收数据,所以我相信您将经历附加更改,而不是破坏附加更改,只要您发现它们而无需返工就可以安全添加。 / li>
  2. 耦合:这里很少或没有耦合。首先,由于通信是通过消息进行的,因此在播种数据期间,服务之间除了短暂的时间之间没有任何耦合,而且该操作的约定(这不是您可以或应该避免的耦合)

我不会忽略选项1。耦合的数量是相同的,但是从开发角度来说应该很容易做到(不需要采取特殊措施),并且域的稳定性应该意味着它们不会经常更改(正如我已经提到的那样)。

我建议的另一个选择是对3进行细微改动,这不是在启动期间运行该过程,而是观察B和C上的“ ProductAdded和ProductDetailsChanged”事件, A中的产品目录。这将使您的部署速度更快(如果发现任何问题/错误,则更容易解决)。


编辑2020-03-03

在确定整合方法时,我有一个特定的优先顺序:

  1. 一致性的代价是什么?我们能否接受A中更改的事物与B和C中反映的事物之间几毫秒的不一致?
  2. 您需要时间点查询(也称为时间查询)吗?
  3. 数据是否有真实来源?拥有它们并被视为上游的服务?
  4. 如果有所有者/真理的唯一来源是否稳定?还是我们希望看到频繁的重大变化?

如果不一致的代价很高((基本上,A中的产品数据需要尽快与B和C中缓存的产品保持一致)),那么您将无法避免需要接受不可用性并提出同步请求( (例如网络/休息请求)从B&C到A来获取数据。意识到!这仍然不意味着事务一致,而只是最小化了不一致的窗口。如果绝对肯定地必须立即保持一致,则需要重新调整服务范围。但是,我非常坚信这应该不是问题。从经验来看,公司很少接受几秒钟的不一致实际上是非常罕见的,因此您甚至不需要发出同步请求。

如果您确实需要时间点查询(我在您的问题中没有注意到,因此未包括在上面,也许是错误的),那么在下游服务上维护此点的成本非常高(您会需要在所有下游服务中复制内部事件投影逻辑),以使决策变得清晰:您应将所有权留给A,并通过Web请求(或类似请求)临时查询A,而A应该使用事件来源来检索所有事件您当时知道要投射到状态并返回状态。我想这可能是选项2(如果我理解正确的话?),但代价是,虽然时间耦合要好于重复事件和投影逻辑的维护成本。

如果您不需要时间点,并且没有明确的数据所有者(在我最初的回答中,我确实是根据您的问题假设的),那么将是一个非常合理的模式在每个服务中分别保存产品的表示形式。当您更新产品数据时,可以通过向每个请求并行发出Web请求来并行更新A,B和C,或者您有一个命令API,可以向A,B和C中的每个发送多个命令。数据的本地版本可以完成工作,这可能是陈旧的,也可能不是陈旧的。这不是上面的任何选项(尽管可以使其与选项3接近),因为A,B和C中的数据可能不同,并且产品的“整体”可能是所有这三个数据的组合来源。

了解真相来源是否具有稳定的合同很有用,因为您可以使用它来使用域/内部事件(或您在事件源中存储为A的存储模式中存储的事件)在A和服务B之间进行集成和C。如果合同稳定,则可以通过域事件进行集成。但是,在频繁更改或消息合同足够大而又使传输成为问题的情况下,您还需要额外担心。

如果您有一个清晰的所有者,并且期望它具有稳定的避孕套,则最佳选择将是选项1;订单将包含所有必要的信息,然后B和C将使用事件中的数据执行其功能。

如果按照您的选择3合同可能会更改或经常中断,则退回Web请求以获取产品数据实际上是更好的选择,因为维护多个版本要容易得多。因此,B会在v3产品上提出请求。