【问题标题】:Microservice CQRS separate building (writing) the query model and reading the model微服务 CQRS 分离构建(写入)查询模型和读取模型
【发布时间】:2019-11-16 17:01:11
【问题描述】:

以下场景

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

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

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

换句话说。这个服务接收 60,000 次写入操作,但只有十几个读取操作。

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

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

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

【问题讨论】:

  • 我理解正确吗?你在同一个微服务中实现 CQRS 并且它们使用同一个数据库?
  • @xargs-mkdir 否,消息被发送到事件存储,该事件存储持久化事件并将相同的消息发送到另一个微服务将其存储为查询模型的流中。

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


【解决方案1】:

假设

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

问题

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

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

基于此,将这部分域拆分为 2 个微服务并不是最好的方法(再次强调 是基于我的假设你需要几乎实时读取微服务数据)。

可能的解决方案

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

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

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

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

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

    • 数据库部分 在这种方法中,您将使用物化视图,该视图将用于读取与您的主数据库或写入微服务数据库相同的数据库。例如,如果您使用 PostgreSQL,您可以使用 Marten(https://jasperfx.github.io/marten/) 进行事件溯源和存储事件。它适用于 .NET,但我想其他语言也有其他解决方案。 Marten 的优点是 您可以生成物化视图(称为投影视图),这些视图会在聚合/模型更改时立即生成。 意味着如果您使用事件源更改了某些客户对象,您将发布一个将被存储的事件 到数据库(使用 Marten)。在那之后,貂将更新您的投影视图(这将是您的数据库中的一个表,如 CustomersProjection),仅应用最后一个事件。这是非常高效的,您的视图将在事件发布后立即更新。这样您就可以利用现有的事件溯源实现。

    • 后端 CQRS 部分 与之前的方法一样,服务器/后端将被拆分为 2 个微服务。与这里的其他方法不同 一切都将在一个物理数据库中。对于 CQRS,您仍会继续使用它,但仅限于您的 服务器/后端级别。当涉及到数据库级别时,您将从两个微服务物理访问同一个数据库。 只会有一个逻辑拆分,并且为两者使用相同的数据库也有一些缺点。即使你将一切合二为一 数据库,您只能从 Read 微服务访问 Projection-views,从 Write 微服务访问所有其他表。 您可以通过多种方式解决此问题,以在代码级别添加限制以不访问特定表。例如 如果您将一些 ORM 与 .NET 或 Java 一起使用,您可以轻松地做到这一点。对于其他技术,也有类似的解决方案。

    • 这样做的问题是您将为两个微服务使用一个数据库。

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

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

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

一般:

打破“每个服务一个数据库”是一种罪过还是可以 考虑以书面形式(到数据库)的服务拆分和 读取/呈现(来自同一数据库)作为一项服务。

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

写入数据库需要比读取更大的扩展 数据库。

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

总结:

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

【讨论】:

  • 感谢您如此详尽的回答。您对使用 Cassandra 等分布式数据库有何看法?两个服务都读取和写入集群的位置。
  • 对于这种特殊情况,它可能是一个不错的选择。您需要一个非常高性能的数据库来处理大量写入操作。 Cassandra 可以处理数量惊人的写入量(阅读medium.com/netflix-techblog/…)。它还有一种复制机制(多 DC 复制),类似于我为 SQL Server 描述的机制,因此您也可以使用这种方法。另请阅读 Cassandra Turnable Consistency (teddyma.gitbooks.io/learncassandra/content/replication/…)
  • 一般来说,Cassandra 将拥有大部分对您来说很重要的功能,例如繁重的写入操作处理和读取复制。根据我从您的问题中获得的信息,我会说这听起来是一个不错的选择。在答案中,我只是试图给您一个关于哪些方法是可能的并且可以适用的想法,而不管您的数据存储技术如何。来自不同数据存储技术但想法也适用于这种情况的示例,尤其是选项 1 中的复制部分。
  • 基本上它会是一个 2 节点集群,对吧?您认为哪种“可转一致性”是一个不错的选择? Local_one?
  • 是的 2 个节点集群,似乎对于“可旋转一致性” Local_one 将适用,如描述所述:“必须将写入发送到本地至少一个副本节点并成功确认数据中心。”
【解决方案2】:

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

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

  1. 在 CQRS 的众多好处中,其中之一是将数据的读取端和写入端分开,正是针对像您这样的需求,其中存在许多顺序的差异性能要求的量级。您需要能够以不同方式扩展这两个部分,这意味着在微服务世界中拥有两种不同的服务。

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

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

【讨论】:

  • 我已经准备好实现 Cqrs 了吗?不确定你是否看过我的帖子。这是关于更新频繁发生的查询模型我仍然有读/写问题..
  • 我理解,我是说你应该实现两个不同的微服务。并且不用担心“一个服务一个数据库”的原则。
  • 为了进一步扩展我的答案,我说您使用 CQRS 的决定是正确的并且适合此任务。我继续走同一条路,并澄清 CQRS 从字面上断开了数据的写入端与读取端的连接,因此您不必担心使用相同的数据库。您的 CQRS 实施将确保您的安全。
猜你喜欢
  • 1970-01-01
  • 2018-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多