使用事件溯源和 CQRS 的缺点是啥?
Posted
技术标签:
【中文标题】使用事件溯源和 CQRS 的缺点是啥?【英文标题】:What are the disadvantages of using Event sourcing and CQRS?使用事件溯源和 CQRS 的缺点是什么? 【发布时间】:2016-01-21 15:39:36 【问题描述】:事件溯源和 CQRS 非常棒,因为它使开发人员摆脱了对一个预先建模的数据库的困扰,除非有大数据迁移项目,否则开发人员必须在应用程序的整个生命周期中使用该数据库。 CQRS 和 ES 还具有其他优势,例如扩展事件存储、审计日志等,这些优势已经遍布互联网。
但是有什么缺点呢?
以下是我在研究和编写小型演示应用程序后能想到的一些缺点
-
复杂:有人说 ES 很复杂。但我想说,拥有一个复杂的应用程序比一个复杂的数据库模型要好,在该模型上你只能使用查询语言(多个连接、索引等)运行非常受限的查询。我的意思是,像 Scala 这样的编程语言有非常丰富的集合库,可以非常灵活地生成一些非常复杂的聚合,还有 Apache Spark 可以轻松查询分布式集合。但是数据库总是受限于它的查询语言能力,并且分布式数据库比分布式应用程序代码更难(只需在另一台机器上部署另一个实例!)。
磁盘空间使用率高:事件存储最终可能会使用大量磁盘空间来存储事件。但是我们可以每隔几周安排一次清理并创建快照,也许我们可以将历史事件本地存储在外部 HD 上,以防我们将来需要旧事件?
高内存使用率:每个域对象的状态都存储在内存中,这可能会增加 RAM 使用率,并且我们都知道 RAM 是多么昂贵。 大问题!!因为我很穷!有什么解决办法吗?可以使用 Sqlite 而不是将状态存储在内存中吗?我是否通过在我的应用程序中引入多个 Sqlite 实例使事情变得更复杂?
启动时间较长:失败或软件升级启动速度较慢,具体取决于事件的数量。但是我们可以使用快照来解决这个问题吗?
最终一致性:某些应用程序存在问题。想象一下,如果 Facebook 使用 Event sourcing 和 CQRS 来存储帖子并考虑到 Facebook 系统的繁忙程度,如果我发布了帖子,我会在第二天看到我的 fb 帖子:)
事件存储中的序列化事件:事件将存储事件存储为序列化对象,这意味着我们无法查询事件存储中的事件内容,无论如何都不鼓励这样做。而且我们将来无法为该事件添加另一个属性。解决方案是将事件存储为 JSON 对象而不是序列化事件?但这是个好主意吗?或者添加更多事件来支持对原始事件对象的更改?
有人可以评论我在这里提出的缺点,如果我错了,请纠正我,并提出我可能错过的任何其他建议?
【问题讨论】:
为什么每个域对象的状态都存储在内存中?域对象在需要时从事件中重新创建。为什么你会认为 FB 不会使用事件溯源,只是因为他们很忙?我的理解是,他们使用一个写入主机和几个读取从机,它们“最终”同步,所以他们确实使用最终一致性,这就是为什么有时你可以发布一些东西,然后在你的提要中看不到它。 通常每个对象不会有数千个事件,但如果你这样做可能不会像你想象的那么慢(简单的选择没有连接来获取事件)然后应用到内存中。如果在性能测试中确实被证明是一个问题,您总是可以“快照”对象的事件。 事件溯源并不意味着最终的一致性。你可以做 ES 并立即保持一致。 您也可以立即与CQRS保持一致。 @cmr 看来您将许多不同的想法混为一谈。 Cqrs、事件溯源和最终一致性都是可以独立实现的不同想法 【参考方案1】:这是我的看法。
CQRS + ES 通过拥有丰富的领域对象、简单的数据模型、历史跟踪、对并发问题的更多可见性、可扩展性等等,可以使复杂软件系统中的事情变得更加简单。它确实需要以不同的方式思考系统,因此可能很难找到合格的开发人员。但是 CQRS 使开发人员之间的职责分离变得更加简单。例如,初级开发人员可以只使用读取端,而无需触及业务逻辑。
数据副本肯定需要更多磁盘空间。但是现在存储相对便宜。它可能需要 IT 支持团队进行更多备份,并计划在出现问题时如何恢复系统。但是,如今的服务器虚拟化使其成为更简化的工作流程。此外,在没有单一数据库的情况下,在系统中创建冗余要容易得多。
我不认为更高的内存使用是一个问题。业务对象水合应该按需进行。对象不应保留对已持久化事件的引用。并且事件水合应该仅在持久化数据时发生。在读取方面,您没有通常在分层系统中发生的 Entity -> DTO -> ViewModel 转换,并且您不会像功能齐全的 ORM 通常那样进行任何类型的对象更改跟踪。大多数系统执行的读取次数明显多于写入次数。
如果您使用多个异构数据库,由于各种数据上下文的初始化,较长的启动时间可能是一个小问题。但是,如果您使用 ADO .NET 之类的简单工具与事件存储进行交互,并为读取端使用微 ORM,则系统将比任何功能齐全的 ORM 更快地“冷启动”。这里重要的是不要使您访问数据的方式过于复杂。这实际上是 CQRS 应该解决的问题。正如我之前所说,读取端应该为视图建模,并且没有任何重新映射数据的开销。
根据我的经验,两阶段提交可以很好地适用于不需要为成千上万的用户扩展的系统。您需要选择与分布式事务协调器配合良好的数据库。例如,PostgreSQL 可以很好地用于读取和写入单独的模型。如果系统需要针对大量并发用户进行扩展,则必须在设计时考虑到最终的一致性。在某些情况下,您将拥有不使用 CQRS 的聚合根或上下文边界以避免最终一致性。这对领域的非协作部分很有意义。
如果您为事件存储选择正确的数据库,您可以以 JSON 或 XML 等序列化格式查询事件。这应该只用于分析目的。除了聚合根 id 和事件类型之外,系统内部的任何内容都不应查询事件存储。该数据将被索引并存在于序列化事件之外。
【讨论】:
感谢您提供非常详细的答案。这让我更清楚了。 我只想补充一点。我们应该避免两阶段提交。最好的解决方案是尽可能订阅事件存储并处理,但不要将事件存储绑定到分布式事务【参考方案2】:仅对第 5 点发表评论。有人告诉我,Facebook 确实使用具有最终一致性的 ES,这就是为什么有时您会看到帖子在发布后消失并重新出现的原因。
通常,您的浏览器正在访问的读取模型位于“靠近”您的位置,但在您发布帖子后,SPA 会切换到接近您的写入模型的读取模型。写入模型(事件)和读取模型之间的紧密联系意味着您可以看到自己的帖子。
但是,15 分钟后,您的 SPA 切换回第一个更接近的读取模型。如果包含您的帖子的事件尚未传播到该读取模型,您将看到您自己的帖子消失,只是稍后再出现。
【讨论】:
非常有趣!【参考方案3】:我知道这个问题被问到已经快 3 年了,但this article 仍然可能对某人有用。重点是
使用快照进行缩放 数据的可见性 架构更改 处理复杂的域 需要向大多数新团队成员解释一下【讨论】:
【参考方案4】:事件溯源和 CQRS 非常棒,因为它让开发人员摆脱了对一个预先建模的数据库的困扰,除非有大数据迁移项目,否则开发人员必须在应用程序的整个生命周期中使用该数据库。
这是一个很大的误解。关系数据库的发明正是为了模型的发展(感谢简单的二维表,而不是预定义的层次结构)。通过确保封装数据访问的视图和过程,逻辑和物理模型可以独立发展。这也是 SQL 用同一种语言定义 DDL 和 DML 的原因。一些 RDBMS 还允许将所有这些演变作为基于 Oracle 版本的重新定义进行版本控制和在线部署(持续交付)。
大数据结构是预定义的,只能使用为此结构开发的代码读取。可以立即使用,但如果没有确切的版本、语言编译器或解释器,您将很难在 10 年后阅读它。
【讨论】:
【参考方案5】:我希望不要迟到尝试给出答案。在这几个月里,我对这个论点进行了大量研究,目的是为我的架构的某些部分实施生产级解决方案,让 ES 有意义
复杂:其实它不应该是复杂的,它的使命是简单到致命。如何?将所有复杂性从业务逻辑代码推到基础设施代码。数据访问应该由还不够成熟的框架来完成。尽管如此,在 ES/CQRS 比赛中仍然没有明确的赢家,可能是因为仍然是一种小众/时髦的方法(?)所以一些团队正在推出自己的解决方案或采用一些现成的技术,如 Axon
高磁盘空间使用率:我会说更多,我会说 * 可能无限* 磁盘使用率。但是如果你走向 ES,你也有很好的理由容忍这个明显的缺点。让我们给出其中的一些:
审计日志:数据存储是一个事件日志,我们已经知道了。金融应用程序或每个关键任务/安全可能需要一个集中的审计日志,以便能够说明谁在哪个时刻做了什么。 ES 提供了这个功能……你还可以用一些对业务有意义的元数据来装饰你的事件条目(例如,与某些 API 消费者身份相关的事务 Id、操作的严重性级别……)
高并发:在某些系统中,逻辑资源状态被许多客户端以并发方式改变。这些是游戏、物联网平台等。记录事件而不是更改状态表示可能是提供事件总顺序的聪明方法。另一种方法是将同步的东西委托给数据库。但如果你是 ES,这不是你想要的
分析 假设您拥有大量具有大量业务价值的数据,但您仍然不知道哪些数据。多年来,我们通过使用不同信息模型(OLAP 多维数据集)转换数据组织来从应用程序信息中提取知识。事件存储再次提供了类似的开箱即用的东西。事件日志是最原始的信息表示形式,您可以有多种方式来处理它们,批量处理或对存储的事件做出反应
高内存使用率:我认为构建投影后应该是一样的
更长的启动时间:如果读取端缓存其投影并“记住”最后一个更新事件,则不应重新应用整个事件序列。快照是一种缓解措施,但如果你做了很多快照,你可能在 ES 上做出了错误的选择。我认为这个问题在微服务生态系统中是次要的,可以在不中断服务的情况下屏蔽启动时间。事实上,当您将 ES/CQRS 应用到微服务中时,您可以充分利用它
最终一致性:归咎于 CAP 定理,而不是 ES。许多非 ES/CQRS 必须处理这个问题,但在很多场景中这并不是真正的问题。这些是 ES 非常适合的场景。并且您可以将 ES 和非 ES 服务混合到同一个平台中
事件存储中的序列化事件:如果非序列化事件表示很重要,您可以使用面向文档的数据库,但如果您这样做是为了对事件有效负载进行查询,您缺少 ES/CQRS 的要点。 ES 意味着将所有数据操作从 DB 端转移到应用层,每个部分都在快速变化,并且都是无状态的。这增强了可扩展性和容错性,并提供了塑造团队组织的方法,例如让前端的男孩/女孩轻松地用 javascript 编写他/她的 BFF。
我希望将这一原则付诸实践并取得良好的效果,并利用这种令人兴奋的方法的好处
【讨论】:
以上是关于使用事件溯源和 CQRS 的缺点是啥?的主要内容,如果未能解决你的问题,请参考以下文章