【问题标题】:CQRS, Event Sourcing and ScalingCQRS、事件溯源和扩展
【发布时间】:2018-11-15 20:40:12
【问题描述】:

很明显,基于这些模式的系统很容易扩展。但是我想问你,具体怎么做?关于可扩展性,我有几个问题:

  1. 如何扩展聚合?如果我将创建aggregate A 的多个实例,如何同步它们?如果其中一个实例处理命令并创建一个事件,该事件应该传播到该聚合的每个实例吗?
  2. 不应该存在一些业务逻辑来请求哪个聚合实例?因此,如果我发出多个适用于aggregate A (ORDERS) 并适用于一个特定订单的命令,则将其交付给同一个实例是有意义的。还是?

在这篇文章中:https://initiate.andela.com/event-sourcing-and-cqrs-a-look-at-kafka-e0c1b90d17d8, 他们正在使用带有分区的Kafka。因此,用户管理服务 - 聚合是可扩展的,但仅订阅主题的特定分区,其中包含特定用户的所有事件。

谢谢!

【问题讨论】:

    标签: cqrs event-sourcing


    【解决方案1】:

    如何扩展聚合?

    • 仔细选择聚合,确保您的命令在多个聚合中合理分布。您不希望有可能从并发用户接收大量命令的聚合。

    • 序列化发送到聚合实例的命令。这可以通过聚合存储库和命令总线/队列来完成。但对我来说,最简单的方法是使用聚合版本控制进行乐观锁定,如 this post by Michiel Rook

    • 中所述

    请求聚合的哪个实例?

    在我们的reSolve framework 中,我们在每个命令上创建聚合实例,并且不要在请求之间保留它。这工作得非常快 - 获取 100 个事件并将它们减少到聚合状态比在集群中找到正确的聚合实例要快。

    这种方法是可扩展的,让您可以使用无服务器 - 每个命令一次 lambda 调用,并且两者之间没有共享状态。聚合事件过多的罕见情况通过快照解决。

    【讨论】:

    • 那么在您的 reSolve 框架中,您每 100 个事件创建一次快照?
    • 它是可配置的,实际上取决于事件存储数据库和事件大小。但是 100-200 是合理的默认值。
    • 另一个问题。您更喜欢乐观锁定,这意味着您还可以扩展单独的聚合。假设您有命令 WithdrawMoney,并发用户执行了数千次。在无服务器部署的情况下,您如何实现它,因为您有许多彼此之间不通信的 lambda 实例?
    • 首先,来自并发用户的千条命令不应该达到相同的聚合,然后让它更小。如果两个命令同时命中同一个聚合 - 第一个完成的 lambda 将获胜,第二个将在尝试为同一聚合版本保存事件时出现乐观异常。它真正需要做的只是再次应用命令。 Lambda 不必相互交谈。
    • 这应该在发送命令之前完成。 DDD 假定聚合应该能够验证所有业务规则,而无需与其他聚合通信。这里有一篇好文章danielwhittaker.me/2014/11/22/…
    【解决方案2】:

    如何扩展聚合?

    系统中的每条信息都有一个逻辑权限。单个数据的多个权限使您争用。您可以通过创建更小不重叠的边界来扩展写入——每个权限都有更小的职责范围

    To borrow from your example, an example of smaller responsibilities would
    be to shift from one aggregate for all ORDERS to one aggregate for _each_
    ORDER.
    
    It's analogous to the difference between having a key value store with
    all ORDERS stored in a document under one key, vs each ORDER being stored
    using its own key.
    

    读取是安全的,您可以使用多个副本扩展它们。然而,这些副本只是最终一致。这意味着如果您问“FCOJ现在的投标价格是多少?”您可能会从每个副本中得到不同的答案。或者,如果您问“FCOJ 在 10:09:02 的买入价是多少?”那么每个副本要么给你一个答案,要么说“我还不知道”。

    但是如果粒度已经是每个聚合一个命令,我认为这通常是不可能的,并且您确实有很多并发访问,如何解决?如何分散负载并尽可能保持没有冲突?

    粗略的草图 - 它通过一个可以从命令消息的内容计算的密钥存储的每个聚合。对聚合的更新是通过使用该键的比较和交换操作来实现的。

    Acquire a message
    Compute the storage key
    Load a versioned representation from storage
    Compute a new versioned representation
    Store.compare and swap the new representation for the old
    

    要提供额外的流量吞吐量,您需要添加更多无状态计算。

    为了提供存储吞吐量,您可以在更多存储设备之间分配密钥。

    路由层可用于将消息分组在一起 - 路由器使用与以前相同的存储密钥计算,但使用它来选择在计算场中转发消息的位置。然后,计算可以检查它收到的每批消息中是否存在重复键,并一起处理这些消息(交换一些额外的计算以减少比较和交换的次数)。

    合理的消息协议很重要;请参阅 Marc de Graauw 的 Nobody Needs Reliable Messaging。

    【讨论】:

    • 当然,您可以通过创建更小的 nkt 重叠边界来扩展写入。但是,如果粒度已经是每个聚合一个命令,我认为这通常是不可能的,并且您确实有很多并发访问,如何解决?如何分散负载并尽可能保持不冲突?
    【解决方案3】:

    如何扩展聚合?

    聚合实例由它们的事件流表示。每个聚合实例都有自己的事件流。来自一个聚合实例的事件不会被其他聚合实例使用。例如,如果 ID=1 的 Order Aggregate 创建了一个 ID=1001 的 OrderWasCreated 事件,则该事件将永远不会用于再水化其他 Order Aggregate 实例(ID=2,3,4...)。

    话虽如此,您可以通过基于聚合 ID 在事件存储上创建分片来水平扩展聚合。

    如果我将创建聚合 A 的多个实例,如何同步它们?如果其中一个实例处理该命令并创建一个事件,该事件应该传播到该聚合的每个实例吗?

    你没有。每个 Aggregate 实例都与其他实例完全分离。

    为了能够水平扩展命令的处理,建议每次从事件存储中加载聚合实例,方法是重放所有之前生成的事件。您可以进行一项优化来提高性能:聚合快照,但建议仅在确实需要时才这样做。 This 回答会有所帮助。

    不应该存在一些业务逻辑来请求聚合的哪个实例吗?因此,如果我发出多个适用于聚合 A (ORDERS) 并适用于一个特定订单的命令,则将其交付给同一个实例是有意义的。还是?

    您假设聚合实例在某些服务器的 RAM 上连续运行。你可以这样做,但这样的架构非常复杂。例如,当其中一台服务器出现故障并且必须由另一台替换时会发生什么?很难确定哪些实例居住在那里并重新启动它们。相反,您可以拥有许多 无状态 服务器来处理任何聚合实例的命令。当命令到达时,您识别聚合 ID,通过重播所有以前的事件从事件存储中加载它,然后它可以执行命令。执行命令并将新事件持久化到事件存储后,您可以丢弃聚合实例。到达同一聚合实例的下一个命令可以由任何其他 无状态 服务器处理。因此,可伸缩性仅取决于事件存储本身的可伸缩性。

    【讨论】:

    • 您好,感谢您的回答。我现在很困惑,我不确定我是否明白了聚合 ID 的意义。聚合 id 定义聚合本身 - ORDERS?或唯一的项目 - 一个带有 id 的订单...因为如果聚合 id 是实体的 id,我很清楚我将只加载属于该特定订单的事件。在这种情况下,创建订单的 1000 个并行用户只是不同的 1000 个聚合。对吗?
    • 还有一个问题,如果我需要做一些整体验证,对系统中的某个读取模型进行验证是否有效?或者答案是否定的,我应该重新考虑我的界限。
    • @OndrejTomcik 是的,聚合的 ID 是例如订单的 ID
    • @OndrejTomcik 聚合体不得超出其边界,因此绝对不
    猜你喜欢
    • 2019-01-31
    • 2019-09-22
    • 2018-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-24
    相关资源
    最近更新 更多