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

Microservice CQRS separate building (writing) the query model and reading the model

以下场景

Receiving 60,000 messages per minute from a queue. REST API serving the data from those messages 10 times per minute.

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

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

也就是说。此服务接收 60,000 次写入操作,但只有十几个读取操作。

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

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

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

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

两个观点帮助你思考这个问题:

  1. 在 CQRS 的众多好处中,其中之一是分离数据的读取端和写入端,完全符合像您这样的要求,其中性能要求相差很多个数量级。您需要能够以不同方式扩展这两个部分,这意味着在微服务世界中拥有两种不同的服务。

  2. 使用真正的 CQRS,您倾向于 在数据的写入和读取部分之间没有 relationship/linkage。一个完美的例子是使用 RDBMS 存储写入端,并使用文档存储(如 MongoDB 甚至像 ElasticSearch 这样的索引)用于查询端,因为它支持聚合数据结构。

恕我直言,如果您希望能够单独扩展并且不希望写入和读取之间存在任何争用,那么两个单独的服务是可行的方法。

假设

据我了解,问题是您的写入模型需要尽快反映读取模型状态,因为您已经 每分钟只需读取 10 次,他们就需要实时或接近实时地反映真实状态。

问题

基于此假设将此域拆分为 2 micro-services 并使用 CQRS 将无法解决该问题。 为什么?

因为如果您有写入 micro-service 和读取 micro-service,并且您更新读取 micro-service 对于您从 Write micro-service 发布的事件,您将遇到延迟问题。 这意味着您将有大约 50-100 毫秒的某种最小延迟(大部分时间 延迟会更大) 直到您的 Read micro-service db 与您的 Write micro-service db 同步。 这是正常的,这是您在使用分布式时需要考虑的事情 使用队列的系统。

基于此将域的这一部分拆分为 2 micro-services 并不是最好的方法(同样这 基于我的假设,您需要几乎实时读取 micro-service 数据。

可能的解决方案

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

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

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

    • 后端的CQRS部分 当谈到 CQRS 时,您仍然会继续使用 2 micro-services(写入和读取)。写入 micro-service 将使用 您的主 SQL 服务器实例和您的读取 micro-service 将使用主数据库的只读副本。记住 这个只读副本是单独机器上的单独数据库 运行。因此,基于此,您将满足规则:“每个数据库 1 个 micro-service”。 当然,如果您使用的是 Microsoft Sql 服务器,则可以使用此选项。

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

    • 数据库部分 在这种方法中,您将使用物化视图,该视图将用于读取与主数据库或写入 micro-service 数据库相同的数据库。例如,如果您使用 PostgreSQL,则可以使用 Marten(https://jasperfx.github.io/marten/) 进行事件溯源和存储事件。它适用于 .NET,但我猜也有适用于其他语言的其他解决方案。 Marten的好处是 您可以生成物化视图(称为 Projection-views),一旦 Aggregate/Model 更改就会生成。 意味着如果您使用事件源更改了一些客户对象,您将发布一个将被存储的事件 到数据库(使用 Marten)。之后貂将更新您的 Projection-view(这将是您数据库中的 Table,如 CustomersProjection),仅应用最后一个事件。这是非常高效的,您的视图将在事件发布后立即更新。这样您就可以利用现有的事件溯源实现。

    • back-end CQRS 部分 server/back-end 边将被拆分为 2 micro-services 与之前的方法一样。与这里的其他方法不同 一切都在一个物理数据库中。当谈到 CQRS 时,您仍会继续使用它,但仅限于您的 server/back-end级。当谈到数据库级别时,您会从两个 micro-services 物理上访问同一个数据库。 这只是一个逻辑拆分,并且对两者使用相同的数据库也有一些缺点。即使你会拥有一切 数据库,您只能从读取 micro-service 访问 Projection-views,从写入 micro-service 访问所有其他表。 有多种方法可以解决此问题,以在代码级别上添加限制以不访问特定表。例如 如果您将一些 ORM 与 .NET 或 Java 一起使用,您可以轻松地做到这一点。对于其他技术,也有类似的解决方案。

    • 这样做的问题是您将同时使用一个数据库 micro-services。

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

    • 如果您将域的某些部分的 Application/Domain 拆分为 micro-services,则使用 CQRS不仅从数据库点分离读取和写入的观点也是从服务器扩展点 看法。 从数据库的角度来看,如果你使用 2 个数据库,你甚至可以选择不同的技术 您的写入和读取数据库。从服务端来看,好处是您可以独立于 写部分,反之亦然。 在你的情况下,如果你每 1 分钟只有 10 次阅读意味着你没有大 使用单独的数据库加载您的数据并且 micro-service 不是必需的(在这种情况下我什至会说矫枉过正)。 每分钟在同一台服务器上加载 10 次以上,其中写入将与 阅读几乎没有区别。但我不知道这是否只是 现在或者要求会改变并且阅读请求会增加。现在没有必要,并且 我会把所有东西都放在一个 micro-service 和数据库中。 在我之前的一个项目中,我们有一个确切的场景,我们有一个巨大的基于 micro-service 的架构,并且在 我们使用 CQRS 的部分将特定的 Sub-Domain 拆分为 2 micro-services 具有其专用数据库,但是 在域的其他部分,我们没有使用 CQRS,因为它在域的那部分对我们来说根本没有任何意义。

请记住,此提案在某些情况下特定于某些数据库技术,例如 SQL Server、PostgreSQL 或 相似但对您来说重要的是想法和方法。无论您使用什么数据库,这些事情中的大部分都可以完成。

总的来说:

Is it a sin to break the 'one database per service' or is it okay to consider a service split in writing (to database) and reading/presenting (from same database) as one service.

我想说的是每条规则都有例外,如果使用 2 db 的 CQRS 会让你的生活变得艰难并且你会遇到问题 因为它而使用你的系统或域那么这意味着你没有正确使用 pattern/practices 或者你 为您的案例使用了错误的模式。记住 模式是用来解决常见问题的,如果它们不适用于特定情况,请不要使用它们。 在某种意义上 micro-services 事情变得更加复杂,因为很多事情都被改编了 以满足您的业务需求。这很好,因为目标是为客户提供最佳解决方案。 即使您和您的团队自己发现可以使用 2 micro-services 并使用 1 个数据库作为 最好的解决方案,去吧。不要将它作为整个架构的规则,因为它在 micro-services 中没有实践 世界,但一如既往,如果你有充分的论据,你就可以打破规则。

Writing to the database needs to significantly scale more than reading the database.

从 server/back-end 的角度来看,这根本不是问题,因为您可以水平缩放,并根据需要拥有尽可能多的 micro-service 运行 实例。例如,同时拥有 10 个 运行 只是为了写入就可以了。除了每分钟 10 次读取之外,在这个规模上没有什么可担心的(从某种意义上说,所有内容都在一个 micro-service 读取和写入中)。当涉及到扩展数据库的时候。这是另一个话题。将读取分离到专用数据库无助于扩展写入数据库中的数据。为了解决这个问题,还有其他事情需要考虑:查询优化、添加适当的索引、数据分片、数据历史化等。但这是另一个话题了。

总结:

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