播种微服务数据库


10

给定服务A(CMS)来控制模型(产品,让我们假设它具有的唯一字段是id,标题,价格)以及服务B(运费)和服务C(电子邮件)必须显示给定模型的方法是什么在事件采购方法中跨这些服务同步给定的模型信息?我们假设产品目录很少更改(但确实会更改),并且有管理员可以非常频繁地访问货运和电子邮件数据(示例功能为:B:display titles of products the order contained和C:)display content of email about shipping that is going to be sent。每个服务都有自己的数据库。

解决方案1

发送事件内有关产品的所有必需信息-这意味着以下结构order_placed

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

关于服务B和C的产品信息存储在表的productJSON属性orders

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

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

解决方案2

在事件内仅发送产品的GUID-这意味着以下结构order_placed

{
    order_id: [guid],
    product_id: [guid]
}

在服务B和C上,产品信息存储在表的product_id属性orders

必要时,服务B和C通过对A/product/[guid]端点执行API调用来检索产品信息

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

解决方案3

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

{
    order_id: [guid],
    product_id: [guid]
}

关于服务B和C的产品信息存储在products表中;仍然product_idorders桌上,但是products在A,B和C之间有数据复制;B和C可能包含与A不同的产品信息

产品信息是在创建服务B和C时播种的,并且只要通过调用A/product端点(显示所有产品的必需信息)或通过对A进行直接DB访问并复制给定的必需产品信息来更改有关产品的信息,就可以更新产品信息。服务。

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


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


关于updating event history-在事件来源中,事件历史是您的真理之源,切不可更改,而应向前迈进。如果事件发生更改,则可以使用事件版本控制或类似的解决方案,但是在重播事件直到特定时间点时,数据状态应与该点相同。
不,

关于存储用于查询的数据(方案等)以及要添加/删除的字段等。我们当时使用了cosmosDB,当时的数据存储在JSON中。然后,唯一需要版本控制的是事件和/或命令。您还需要更新终结点合同和值对象,其中包含响应来自客户端(Web,移动设备等)查询的数据。没有字段的较旧数据将具有默认值或空白,这将适合企业,但事件历史记录保持不变,并且只会向前移动。
不,

@Nope,updating event history我的意思是:遍历所有事件,将它们从一个流(v1)复制到另一个流(v2),以保持一致的事件模式。
eithed

顺便说一句,在商业/电子商务领域中,鉴于价格经常变化,您可能想要捕获所述的价格。在捕获到实际订购时,显示给用户的价格可能会有所不同。解决问题的方法有很多,但这是应考虑的一种。
CPerson

@CPerson是的-价格可能是事件本身传递的属性之一。在另一方面,URL用于图像事件可以存在于(表示意图的display image at the point when purchase was made)或不能(表示意图display current image as it within catalog
eithed

Answers:


3

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

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

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

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

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

推荐读物:外部数据,内部数据,Pat Helland,2005年。


是的,我完全同意您在此处编写的内容,解决方案3是我应用的goto解决方案,但是,它不是事件源方法,因为如果我们要重播事件,我们不一定要使用产品的当前状态;我们想使用事件发生时的状态。当然,这可能很好(取决于业务需求)。但如果我们想跟踪更改目录,需要采购活动的为好,并且依赖多少数据是,我们可以更好地回落到解决方案1
eithed

1
我想您已经有了解决方案3。如果您需要与目录重播一致,那么事件源也是如此。您只需要在重新设置种子时重播即可,这可能是在启动时进行的-一旦启动,您只需要查看新事件即可,因此数据量可能不是真正的问题。但是,即使那样,您也可以选择使用检查点(如有必要),即“这里是事件1,000 的状态 ”,因此您可以采用这种方法,现在只需重播事件1,001到当前,而不是整个历史。
Mike B.

2

计算机科学中有两件事,其中之一就是缓存失效。

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

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

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

缓存会增加系统的复杂性,并且会创建难以推理的极端情况以及难以复制的错误。您也可以减轻当新的数据存在,这提供了陈旧数据的风险,可以从比(例如)显示一条消息,一个企业的角度差很多“服务A下跌-请稍后再试”

从Udi Dahan的这篇出色文章中

这些依赖关系慢慢地在您身上蔓延,将鞋带绑在一起,逐渐放慢了开发速度,破坏了代码库的稳定性,其中对系统某一部分的更改破坏了其他部分。这是一千个裁员导致的缓慢死亡,因此没有人确切地确定我们做出的重大决定导致一切变得如此糟糕。

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


1
@SavvasKleanthous“网络可靠”是分布式计算的谬论之一。但是,对于这种谬误的回应不应是“缓存每个其他服务中每个服务的所有数据”(我意识到这有点夸张)。期望服务可能不可用,并将其作为错误条件处理。如果在极少数情况下服务A中断会对业务产生重大影响,请(小心!)考虑其他选择。
Phil Sandler,

1
@SavvasKleanthous还认为(正如我在回答中提到的那样),在许多情况下返回陈旧数据比抛出错误糟糕得多。
Phil Sandler

1
@eithed我指的是这个评论:“但是,如果我们想跟踪目录更改,那么也需要事件源。” 无论如何,您都有正确的想法-产品服务应负责跟踪一段时间内的更改,而不是下游服务。
Phil Sandler

1
此外,存储观察到的数据虽然与缓存有些相似,但不会出现相同的问题。更具体地说,不需要无效。您会在数据发生时获得新版本的数据。您遇到的是延迟的一致性。但是,即使使用Web请求,也存在一个不一致的窗口(尽管很小)。
Savvas Kleanthous

1
@SavvasKleanthous无论如何,我的主要观点是不要尝试解决尚不存在的问题,尤其是要解决具有自身问题和风险的解决方案。选项2是最简单的解决方案,在不能满足业务需求之前,它应该是默认选择。如果您认为选择可行的最简单的解决方案(如您所说)“真的很糟糕”,那么我认为我们只是不同意。
Phil Sandler

2

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

我的2美分:

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

解决方案#1(NOK)

  • 跨多个系统复制数据

解决方案2(确定)

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

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

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

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接收数据,因此我相信您将经历附加更改,而不是破坏附加更改,只要您发现它们而无需返工就可以安全地进行添加。
  2. 耦合:这里几乎没有耦合。首先,由于通信是通过消息进行的,因此,除了在数据播种期间的短暂时间以外,服务之间没有任何耦合,并且该操作的约定(这不是您可以或应该避免的耦合)

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

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


编辑2020-03-03

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

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

如果不一致的代价很高(基本上,A中的产品数据需要尽快与B和C中缓存的产品保持一致),那么您就无法避免需要接受不可用性并进行同步请求(例如Web / rest请求)从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产品上提出要求。


是的,我同意。虽然ProductAddedProductDetailsChanged添加跟踪产品目录的变化,我们需要保持某种方式数据库之间同步数据,在重播情况下事件的复杂性,我们需要进入目录数据从过去。
eithed

@eithed我更新了答案以扩大我所做的某些假设。
SAVVAS Kleanthous
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.