微服务CQRS分别构建(编写)查询模型和读取模型

时间:2019-07-06 11:15:55

标签: design-patterns microservices scaling cqrs event-sourcing

以下情况

  

每分钟从队列中接收60,000条消息。 REST API每分钟10次提供这些消息中的数据。

我有一个带有事件源和CQRS的微服务架构。因此,我的命令已经与查询部分分开了。问题在于同步查询并查询它,而不是命令部分。

每隔几分钟,就会使用event-sourcing模式发送并存储约60,000个命令作为事件。通过CQRS,实际数据(不是事件)被同步到另一个服务,该服务将其存储在数据库中。同时每隔几分钟只能读取十几次数据。

换句话说。这项服务可接收60,000次写入操作,但只能进行十二次读取操作。

我真的很想坚持微服务的设计模式,也就是one database per service,但是出于扩展的原因,我认为在我的场景中这是不可行的。与读取数据库相比,写入数据库需要更大的扩展规模。

我看到了similar question,但答案是建议使用已经实现的CQRS。之前有人告诉我要删除事件源,但这仍然给我留下了6万次写入和10次读取。

用于独立扩展读写的架构应该是什么?我正在考虑创建两个单独的服务,但这将违反one database per service模式。

2 个答案:

答案 0 :(得分:1)

假设

据我了解,问题在于您的写入模型需要尽快反映出读取模型的状态,因为 每分钟只有10次读取,它们需要实时或接近实时反映真实状态。

问题

基于此假设,将该域划分为2个微服务并使用CQRS将无法解决该问题。 为什么?

因为拥有写微服务和读微服务,并且您更新了读微服务 通过您从Write微服务发布的事件,您将遇到延迟问题。 这意味着您将需要大约50-100毫秒的某种最小延迟(大部分时间 延迟会更大) 直到您的读取微服务数据库与您的写入微服务数据库同步。 这是正常现象,这是使用分布式系统时需要考虑的事项 系统使用队列。

基于此,将Domain的这一部分划分为2个微服务不是最佳方法(再次 基于我的假设,即您几乎需要实时读取微服务数据。

可能的解决方案

你能做什么? 您可以在这里做几件事:

  1. 选项-数据库复制和CQRS

    • 数据库部分 同样,如果您需要最新的数据,则可以使用诸如Write数据库的只读复制之类的方法。 SQL Server开箱即用地提供了类似的功能。我在一个项目中使用了它。这意味着 您将拥有另一个数据库,其中SQL Server会将您的数据复制到数据库级别的另一个数据库 (这比完成整个过程,发布到队列并使用来自另一个微服务的消息要快得多)。 该数据库将是您的Write数据库的精确副本,并且它将是只读的。这样,您的10次读取操作将 几乎总是保持最新状态,并且延迟非常低。您可以在此处从SQL Server了解此功能。

    • 后端的CQRS部分 当涉及CQRS时,您仍将继续使用具有2个微服务(写入和读取)的CQRS。 Write微服务将使用 您的主SQL Server实例和“读取”微服务将使用“主”数据库的只读副本。记住 该只读副本是在单独计算机上运行的单独数据库。因此,基于此,您将满足以下规则:“每个微服务1个数据库”。 当然,如果您使用的是Microsoft Sql Server,则可以使用此选项。

  2. 选项-使用事件源生成实例化视图和服务器端CQRS

    • 数据库部分 在这种方法中,您将使用物化视图,该视图将与您的主数据库或写入微服务数据库一起在同一数据库上进行读取。例如,如果您使用PostgreSQL,则可以使用Marten(https://jasperfx.github.io/marten/)进行事件源和存储事件。它适用于.NET,但我想还有其他针对其他语言的解决方案。关于貂的好处是 您可以生成“实体化视图”(称为“投影视图”),这些视图将在“聚合/模型”更改后立即生成。 意味着,例如,如果您使用“事件”来源更改了某些“客户”对象,则您将发布一个事件并存储 到数据库(使用Marten)。之后,貂将仅应用最后一个事件来更新您的Projection-view(这是您的数据库中的一个表,如CustomersProjection)。这是非常出色的活动,发布事件后,您的视图将保持最新状态。这样,您就可以利用已经存在的Event Sourcing实现。

    • 后端CQRS部分 与以前的方法一样,服务器/后端将被拆分为2个微服务。与这里的其他方法不同 一切都将在一个物理数据库中。当涉及到CQRS时,您仍将继续使用它,但仅在您的 服务器/后端级别。当涉及数据库级别时,您将从两个微服务中物理访问同一数据库。 这将是一个逻辑拆分,并且对于两者使用相同的数据库也有一些缺点。即使您将所有东西合而为一 在数据库中,您只能从“读取”微服务访问“投影视图”,而从“写入”微服务访问所有其他表。 您可以通过多种方法来解决此问题,以增加对代码级别的限制,以防止访问特定表。例如 如果您将某些ORM与.NET或Java结合使用,则可以轻松完成此操作。对于其他技术,也有类似的解决方案。

    • 问题在于您将对两个微服务使用一个数据库。

  3. 选项-域的这一部分完全不使用CQRS

    • 如果您将应用程序/域划分为域的某些部分的微服务,则可以使用 CQRS不仅从数据库角度而且从服务器扩展角度分离读取和写入 观点。 从数据库的角度来看,如果您使用2个数据库,您甚至可以选择不同的技术来实现。 您的读写数据库。从服务端的好处是,您可以独立于 写部分,反之亦然。 就您而言,如果每1分钟只有10次读取,则意味着您没有太多 无需使用单独的数据库和微服务对您的数据进行加载(在这种情况下,我什至会说过头了)。 每分钟在同一台服务器上加载10个负载,与写入操作一起处理 读取几乎没有区别。但是我不知道这是否仅仅是 目前,或者需求将发生变化,阅读请求将会增加。现在没有必要了, 我会将所有内容都放在一个微服务和数据库中。 在我之前的一个项目中,我们精确到了一个secnario,其中我们拥有一个庞大的基于微服务的架构,并且 我们使用CQRS在具有专用数据库的2个微服务中将特定的子域划分为两个部分,但是 在域的其他部分,我们没有使用CQRS,因为在域的那部分对我们而言,这根本没有意义。

请记住,这些建议在某些情况下特定于某些数据库技术,例如SQL Server,PostgreSQL或 相似但对您而言重要的是想法和方法。无论您使用什么数据库,这些事情大多数都可以完成。

通常:

  

破坏“每个服务一个数据库”是一种罪过还是可以   考虑以书面形式(到数据库)拆分服务,并且   (从同一数据库中)读取/呈现一项服务。

我想说的是,每条规则都有例外,如果将CQRS与2 db一起使用会使您的生活艰难而又有问题 因为它而与您的系统或域一起工作,则意味着您没有正确使用模式/实践,或者 针对您的情况使用了错误的模式。记住 存在可以解决常见问题的模式,如果它们在特定情况下不可用,则不要使用它们。 在微服务的意义上,事情变得更加复杂,因为很多事情都经过调整 满足您的业务需求。这很好,因为目标是为客户提供最佳的解决方案。 即使您和您的团队发现自己可以使用2种微服务,并使用1种数据库作为 最好的解决方案,继续努力。不要将其作为整个体系结构的规则,因为它不是微服务中的实践 世界,但一如既往如果您有很好的论据,就可以打破规则。

  

写数据库需要比读大得多的规模   数据库。

从服务器/后端的角度来看,这根本不是问题,因为您可以水平扩展并根据需要运行该微服务的实例。例如,让其中的10个同时运行只是为了写操作就可以了。在此范围内,每分钟除了10次读取之外,无需担心(从某种意义上来说,所有内容都在一个微服务中进行读取和写入)。说到扩展数据库。这是另一个话题。将读取分离到专用数据库将无助于扩展写入数据库中的数据。为了解决此问题,还需要考虑其他事项,例如:查询优化,添加适当的索引,数据分片,数据的历史化等等。但这是另一个话题。

摘要:

我的建议是选择1.选项,但前提是您使用SQL Server。另一方面,如果您发现 您当前的数据库技术提供了可比的功能,那么您也可以使用它来实现它。 如果这对您不起作用,我建议您使用3.选项,并放弃此部分(或领域)的CQRS 完全。在我看来,在这种情况下您不需要它。

答案 1 :(得分:0)

要找到问题的答案,您可能必须结合微服务和CQRS的思考过程。

有两种观点可以帮助您思考这个问题:

  1. 在CQRS的众多好处中,其中之一是分离数据的读取侧和写入侧,正是针对您的需求,其中许多订单之间存在差异性能要求的幅度。您需要能够对两个部分进行不同的缩放,这意味着在微服务领域中拥有两种不同的服务。

  2. 使用真正的CQRS,您倾向于在数据的读写部分之间没有关系/链接。一个完美的例子是使用RDBMS来存储写端,并由于支持聚合数据结构而在查询端使用文档存储(如MongoDB甚至是诸如ElasticSearch的索引)。

    < / li>

因此,如果您希望能够分别扩展并且不希望在写入和读取之间发生任何争用,那么两种方法分别是IMHO。