Kri*_*urg 4 scaling design-patterns cqrs event-sourcing microservices
以下场景
每分钟从队列接收 60,000 条消息。REST API 每分钟提供这些消息 10 次的数据。
我有一个带有事件溯源和 CQRS 的微服务架构。所以我的命令已经与查询部分分开了。问题在于同步查询和查询它,而不是命令部分。
每隔几分钟,就会使用event-sourcing模式发送约 60,000 个命令并将其存储为事件。通过 CQRS,实际数据(而不是事件)被同步到将其存储在数据库中的另一个服务。与此同时,数据每隔几分钟仅被读取十几次。
换句话说。该服务接收 60,000 个写入操作,但仅接收十几个读取操作。
我真的很想遵守微服务的设计模式,又名,one database per service但出于扩展原因,我认为这在我的场景中不可行。写入数据库需要比读取数据库显着扩展。
我看到了类似的问题,但答案建议使用 CQRS,我已经实现了。之前有人告诉我要删除事件源,但这仍然给我留下了 60,000 次写入和 10 次读取。
我应该采用什么架构来独立扩展读取和写入?我正在考虑创建两个单独的服务,但这违反了one database per service模式。
假设
据我了解,问题在于您的写入模型需要尽快反映读取模型状态,并且由于每分钟只有 10 次读取,因此它们需要实时或接近实时地反映真实状态。
问题
基于这个假设,将该域拆分为 2 个微服务并使用 CQRS 并不能解决该问题。为什么?
因为如果您有写入微服务和读取微服务,并且使用从写入微服务发布的事件更新读取微服务,则会遇到延迟问题。这意味着您将有大约 50-100 毫秒的某种最小延迟(大多数情况下延迟会更大),直到您的读取微服务数据库与写入微服务数据库同步。这是正常的,这是使用队列使用分布式系统时需要考虑的事情。
基于此,将域的这一部分拆分为 2 个微服务并不是最好的方法(这也是基于我的假设,即您需要几乎实时读取微服务数据)。
可能的解决方案
你能做什么?您可以在这里执行以下操作:
选项 - 数据库复制和 CQRS
数据库部分同样,如果您需要最新的数据,您可以使用写入数据库的只读复制之类的东西。SQL Server 提供了类似的开箱即用的功能。我在我的一个项目中使用了它。这意味着您将拥有另一个数据库,其中 SQL Server 会将您的数据复制到数据库级别的另一个数据库(这比执行整个操作、发布到队列并使用来自另一个微服务的消息要快得多)。该数据库将是您的写入数据库的精确副本,并且是只读的。这样,您的 10 次读取操作几乎总是最新的,且延迟非常低。您可以在此处从 SQL Server 阅读有关此功能的信息。
后端的 CQRS 部分当谈到 CQRS 时,您仍然会继续使用它的 2 个微服务(写入和读取)。写入微服务将使用主 SQL Server 实例,读取微服务将使用主数据库的只读副本。请记住,此只读副本是在单独计算机上运行的单独数据库。因此,基于此,您将满足以下规则:“每个微服务 1 个数据库”。当然,如果您使用的是 Microsoft Sql Server,则可以使用此选项。
选项 - 使用事件源生成物化视图和服务器端 CQRS
数据库部分 在这种方法中,您将使用物化视图,它将用于在与主数据库或写入微服务数据库相同的数据库上进行读取。例如,如果您使用 PostgreSQL,则可以使用 Marten(https://jasperfx.github.io/marten/)进行事件源和存储事件。它是针对 .NET 的,但我想也有针对其他语言的其他解决方案。Marten 的好处是您可以生成物化视图(称为投影视图),这些视图在聚合/模型更改时立即生成。意味着如果您使用事件源更改了某些客户对象,您将发布一个事件并将其存储到数据库(使用 Marten)。之后,貂将更新您的投影视图(这将是数据库中的一个表,如 CustomersProjection),仅应用最后一个事件。这是非常高效的,一旦事件发布,您的视图就会更新。这样您就可以利用现有的事件溯源实现。
与之前的方法一样,后端 CQRS 部分服务器/后端将分为 2 个微服务。与这里的另一种方法不同,所有内容都位于一个物理数据库中。当谈到 CQRS 时,您仍然会继续使用它,但仅限于服务器/后端级别。当涉及到数据库级别时,您将从两个微服务物理访问同一数据库。只会进行逻辑分割,并且对两者使用相同的数据库也有一些缺点。即使您将所有内容都放在一个数据库中,您也只能访问读取微服务中的投影视图和写入微服务中的所有其他表。有多种方法可以解决此问题,以在代码级别添加限制以不访问特定表。例如,如果您将某些 ORM 与 .NET 或 Java 一起使用,您可以轻松地做到这一点。对于其他技术也有类似的解决方案。
这样做的问题是您将为这两个微服务使用一个数据库。
选项 - 对于这部分域根本不使用 CQRS
请记住,这些建议在某些情况下特定于某些数据库技术,如 SQL Server、PostgreSQL 或类似技术,但对您来说重要的是想法和方法。无论您使用什么数据库,大多数事情都可以完成。
一般来说:
打破“每个服务一个数据库”是一种罪孽,还是可以考虑将写入(到数据库)和读取/呈现(从同一数据库)的服务拆分为一项服务。
我想说的是,每条规则都有例外,如果使用带有 2 db 的 CQRS 让你的生活变得困难,并且你在使用你的系统或域时遇到问题,那么这意味着你没有正确使用模式/实践,或者你是对您的案例使用了错误的模式。请记住,模式是用来解决常见问题的,如果不能解决特定情况,则不要使用它们。从微服务的意义上来说,事情变得更加复杂,因为很多事情都需要适应您的业务需求。这很好,因为我们的目标是为客户提供最佳的解决方案。即使您和您的团队发现您可以使用 2 个微服务并使用 1 个数据库作为最佳解决方案,也请继续使用。不要将其作为整个架构的规则,因为它不是微服务世界中的实践,但一如既往,如果您有充分的论据,您可以打破规则。
写入数据库需要比读取数据库显着扩展。
从服务器/后端的角度来看,这根本不是问题,因为您可以水平扩展并根据需要运行尽可能多的微服务实例。例如,让其中 10 个同时运行只是为了服务写入就可以了。除此之外,每分钟 10 次读取在这种规模下无需担心(从某种意义上说,所有读取和写入都在一个微服务中)。当谈到扩展数据库时。这是另一个话题了。将读取分离到专用数据库无助于扩展写入数据库中的数据。为了解决这个问题,还需要考虑其他事情,例如:查询优化、添加适当的索引、数据分片、数据历史化等等。但这是另一个话题了。
概括:
我的建议是使用 1. 选项,但前提是您使用 SQL Server。另一方面,如果您发现当前的数据库技术提供了类似的功能,那么您也可以用它来实现它。如果这对你不起作用,我建议使用 3. 选项并完全放弃这部分(或域)的 CQRS。在我看来,在这种情况下你不需要它。