【问题标题】:Using Kafka as a (CQRS) Eventstore. Good idea?使用 Kafka 作为 (CQRS) Eventstore。好主意?
【发布时间】:2013-07-16 12:28:22
【问题描述】:

虽然我之前遇到过Kafka,但我最近才意识到Kafka 可能被用作CQRSeventstore 的(基础)。

Kafka支持的要点之一:

  • 事件捕获/存储,当然都是高可用性。
  • 发布/子架构
  • 能够重播事件日志,从而使新订阅者能够在事后向系统注册。

诚然,我不是 100% 精通 CQRS / 事件采购,但这似乎非常接近事件存储的应有内容。有趣的是:我真的找不到太多关于 Kafka 被用作事件存储的信息,所以也许我遗漏了一些东西。

那么,为了成为一个好的事件存储,Kafka 缺少什么?它会起作用吗?用它生产?对洞察力、链接等感兴趣。

基本上,系统的状态是根据系统曾经收到的事务/事件保存的,而不是像通常所做的那样仅保存系统的当前状态/快照。 (把它想象成会计中的总账:所有交易最终加起来就是最终状态)这允许各种很酷的东西,但只需阅读提供的链接即可。

【问题讨论】:

  • 嗨 Geert-Jan。回顾过去,你是如何处理这个问题的?我有一个相关的问题(在这里公开:stackoverflow.com/questions/58763727/…)。大多数建议采用 Kafka 的人似乎依赖于附加日志的不变性、高吞吐量和分区顺序保证。我看到与主题内的快速搜索相关的问题(用于实体“重构”)、没有事务原子性和跨分区没有排序(100% 订单保证意味着仅使用 1 个分区 - 杀死并发)
  • 最后没有说服,因为我结束了那个sideproject。所以恐怕没有明确的答案

标签: cqrs event-sourcing apache-kafka dddd


【解决方案1】:

Kafka 旨在成为一个消息传递系统,它与事件存储有许多相似之处,但引用它们的介绍:

Kafka 集群保留所有已发布的消息——无论它们是否 已被消耗 -在可配置的时间段内。例如,如果 保留期设置为两天,然后设置为之后的两天 消息被发布它可以被消费,之后它 将被丢弃以释放空间。 Kafka 的性能是有效的 相对于数据大小而言是恒定的,因此保留大量数据不是 问题。

因此,虽然消息可能会被无限期保留,但预计它们会被删除。这并不意味着您不能将其用作事件存储,但使用其他东西可能会更好。看看EventStore 的替代方案。

更新

Kafka documentation:

事件溯源是一种应用程序设计风格,其中状态更改被记录为按时间排序的记录序列。 Kafka 对非常大的存储日志数据的支持使其成为以这种风格构建的应用程序的出色后端。

更新 2

使用 Kafka 进行事件溯源的一个问题是所需主题的数量。通常在事件溯源中,每个实体(例如用户、产品等)都有一个事件流(主题)。这样,可以通过重新应用流中的所有事件来重构实体的当前状态。每个 Kafka 主题由一个或多个分区组成,每个分区存储为文件系统上的一个目录。随着 znode 数量的增加,ZooKeeper 也会有压力。

【讨论】:

  • 我在看 Kafka 并且有另一个担忧:我没有注意到任何关于乐观并发的东西。理想情况下,我可以说:“仅当对象的最新事件仍为 N 时,才将此事件添加为项目 N+1。”
  • @Darien:我可能会采用 Redis 为 Kafka 提供数据的设置(使用 Redis Notifications)。由于 Redis 允许乐观并发(使用 Watch/multi-exec),这应该可以工作
  • @Darien 我不是事件溯源方面的专家,但我的理解是,一般来说您不需要乐观并发,因为从定义上讲,事件是历史上已经发生的事情的记录。
  • @John 我认为,如果您已经对非冲突事件进行了权威排序,这意味着它们所在的地方就是您的实际事件存储技术,而 Kafka 只是被用作分发的辅助系统他们。
  • 这里也有很有价值的信息:groups.google.com/forum/#!topic/dddcqrs/rm02iCfffUY
【解决方案2】:

我是 Kafka 的原作者之一。 Kafka 可以很好地用作事件溯源的日志。它具有容错性,可扩展到海量数据,并具有内置的分区模型。

我们在 LinkedIn 将它用于此表单的多个用例。例如,我们的开源流处理系统 Apache Samza 带有 built-in support 用于事件溯源。

我认为您没有听到太多关于使用 Kafka 进行事件溯源的消息,主要是因为事件溯源术语在 Kafka 最流行的消费者网络空间中似乎不是很流行。

我已经写了一些关于这种风格的 Kafka 用法here

【讨论】:

  • 打算发布那个链接 :) 很棒的博文。能够评论它会很好,因为我有很多问题。 @Geert-Jan 也看看“Lambda 架构”,这很相似,名字来自 Storm 作者,在许多示例中主要使用某种基于 hadoop 的事件日志
  • @Jay:由于我对这个话题重新产生了兴趣,您能否详细说明一下 Kafka 似乎 被设计为使其发布的消息在设定时间段?如果使用 Kafka 作为事件源,则应该无限期地存储消息。它可能是可配置的,但这会造成问题吗?
  • kafka 和 eventstore 有比较吗?具体来说,我喜欢在名为 Projections 的事件存储中关注 FRP。 Kafka/Samza 中有类似的东西吗?
  • 我也对@Geert-Jan 向 Jay 提出的问题感兴趣。由于每个域聚合需要一个事件流(主题)(想想数百万),Kafka 不适合实际的事件采购事务端。但是,它非常适合将事件从例如获取事件存储。但这仅适用于无限保留的事件(在我们的例子中),除了一些简短的 cmets,这似乎不是 Kafka 支持的用例?我在这里弄错了吗?例如,Samza 假设只有两种情况:基于时间的保留或基于密钥的保留。还有其他的..
  • @eulerfx 假设我们想使用 Kafka 作为事件源系统的存储,应该如何实现乐观锁定/并发?
【解决方案3】:

您可以将 Kafka 用作事件存储,但我不建议这样做,尽管它看起来是不错的选择:

  • Kafka 只保证至少投递一次且有重复 在无法删除的事件存储中。 更新: 在这里,您可以了解为什么 Kafka 如此困难,以及有关如何最终实现此行为的一些最新消息:https://www.confluent.io/blog/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it/
  • 由于不可变性,当应用程序发展和需要转换事件时,无法操作事件存储(当然有向上转换之类的方法,但是...)。曾经可能会说您永远不需要转换事件,但这是不正确的假设,可能存在您备份原始文件但将它们升级到最新版本的情况。这是事件驱动架构中的有效要求。
  • 没有地方可以保存实体/聚合的快照,重放将变得越来越慢。从长远的角度来看,创建快照是事件存储的必备功能。
  • 鉴于 Kafka 分区是分布式的,并且它们难以管理和 备份与数据库比较。数据库更简单:-)

因此,在您做出选择之前,请三思而后行。事件存储作为应用层接口(监控和管理)、SQL/NoSQL 存储和 Kafka 作为代理的组合比让 Kafka 处理这两个角色来创建完整的功能完整解决方案更好的选择。

事件存储是一项复杂的服务,如果您认真考虑在事件驱动架构中应用事件溯源、CQRS、Sagas 和其他模式并保持高性能,它需要的不仅仅是 Kafka 所能提供的。

请随意挑战我的回答!您可能不喜欢我所说的关于您最喜欢的具有许多重叠功能的代理,但是,Kafka 并非设计为事件存储,而是更高级例如,同时处理性能代理和缓冲区以处理快速生产者与慢速消费者场景。

请查看 eventuate.io 微服务开源框架以发现更多潜在问题:http://eventuate.io/

2018 年 2 月 8 日更新

我没有纳入来自 cmets 的新信息,但同意其中一些方面。本次更新更多是关于微服务事件驱动平台的一些建议。如果您对微服务稳健设计和总体上可能的最高性能很认真,我会为您提供一些您可能感兴趣的提示。

  1. 不要使用 Spring - 它很棒(我自己经常使用它),但同时又重又慢。而且它根本不是微服务平台。它“只是”一个帮助您实现一个框架(这背后有很多工作......)。其他框架“只是”轻量级 REST 或 JPA 或不同重点的框架。我推荐可能是一流的开源完整微服务平台,它回归纯 Java 根源: https://github.com/networknt

如果您想了解性能,可以将自己与现有的基准套件进行比较。 https://github.com/networknt/microservices-framework-benchmark

  1. 根本不要使用 Kafka :-)) 这是半开玩笑的。我的意思是,虽然 Kafka 很棒,但它是另一个以代理为中心的系统。我认为未来在无代理的消息系统中。您可能会感到惊讶,但有比 Kafka 系统更快的系统 :-),当然您必须降低到较低级别。看看编年史。

  2. 对于事件存储,我推荐名为 TimescaleDB 的高级 Postgresql 扩展,它专注于大容量的高性能时间序列数据处理(事件是时间序列)。当然,CQRS、事件溯源(重播等功能)是开箱即用的 light4j 框架中构建的,它使用 Postgres 作为低存储空间。

  3. 对于消息传递,请尝试查看 Chronicle Queue、Map、Engine、Network。我的意思是摆脱这种以代理为中心的老式解决方案,并使用微消息系统(嵌入式)。 Chronicle Queue 实际上比 Kafka 还要快。但我同意这不是一个解决方案,你需要做一些开发,否则你去购买企业版(付费版)。最后,从 Chronicle 构建您自己的消息传递层的工作将通过消除维护 Kafka 集群的负担来支付。

【讨论】:

  • 有趣的观点。想详细说明几点? > Kafka 仅保证至少一次交付,并且事件存储中存在无法删除的重复项。你似乎暗示有这样的事情,就像一次交付一样。 afaik(我很确定)分布式系统中没有这样的东西。 2)关于您的第 2 点:(事件溯源 / dddd)思想的经典学派是事件本质上是不可变的。即:它们发生了,无法改变过去。回想起来改变它们的实际用例是什么?谢谢!
  • 1. ) Hazelcast 确保每条消息将被处理一次且只处理一次。 2.) 我不喜欢服务代码中的 _V2 之类的东西,所以要么您将备份以存档并将旧事件重新创建到它们的新版本(您仍然拥有原始事实),或者您可以隐藏/将此功能直接构建到事件中存储快照功能,因此存在单点向上转换 -> 事件存储。您对此有什么解决方案?
  • 1) 至少一次 + 消费者的幂等性。即:检查是否已经看到事件。如果是这样跳过。或者更好的是,具有幂等操作。当然,这并不总是可能的。 2)我从来没有遇到过需要版本事件。我总是将事件本身视为真相的来源,并包含我需要的所有信息。这样做,我从未遇到过需要不同事件结构和/或事件数据的情况。但也许是ymmv。有兴趣了解在什么情况下您实际上需要更新事件。
  • 1.) 可以选择.. 2.) 那么你的数据结构从一开始就是完美的:-) 你很幸运,哈哈。我当前的项目可能不需要它,但是我正在 eventuate.io 的分支上构建一个完整的平台,并结合了一些仅从轻型 eventuate 4j 获取的高性能 JEE 方法……整个讨论不适用于 stackoverflow 上的 cmets ,但如果你有兴趣深入了解我推荐这篇文章:leanpub.com/esversioning/read
  • Kafka 现在只支持一次交付,顺便说一下。更新项目符号 1
【解决方案4】:

是的,您可以将 Kafka 用作事件存储。它工作得很好,尤其是引入了Kafka Streams,它提供了一种Kafka-native方式来将你的事件处理成累积的state that you can query

关于:

能够重播事件日志,从而使新订阅者能够在事后向系统注册。

这可能很棘手。我在这里详细介绍了这一点:https://stackoverflow.com/a/48482974/741970

【讨论】:

    【解决方案5】:

    我会一直回到这个 QA。而且我发现现有的答案不够细致,所以我添加了这个。

    TL;博士。是或否,取决于您的事件溯源使用情况。

    我知道有两种主要的事件源系统。

    下游事件处理器 = 是

    在这种系统中,事件发生在现实世界中并被记录为事实。例如跟踪产品托盘的仓库系统。基本上没有冲突的事件。一切都已经发生了,即使是错的。 (即托盘 123456 放在卡车 A 上,但原定用于卡车 B。)然后通过报告机制检查事实是否存在异常。 Kafka 似乎非常适合这种下游的事件处理应用程序。

    在这种情况下,可以理解为什么 Kafka 人提倡将其作为事件溯源解决方案。因为它与已经在例如点击流中使用的方式非常相似。但是,使用术语事件溯源(而不是流处理)的人可能指的是第二种用法......

    应用程序控制的事实来源 = 否

    这种应用程序声明自己的事件作为用户请求通过业务逻辑的结果。 Kafka 在这种情况下不能很好地工作,主要有两个原因。

    缺乏实体隔离

    此场景需要能够为特定实体加载事件流。这样做的常见原因是为用于处理请求的业务逻辑构建一个瞬态写入模型。在 Kafka 中这样做是不切实际的。使用 topic-per-entity 可以允许这样做,除非当可能有数千或数百万个实体时,这是一个非首发。这是由于 Kafka/Zookeeper 的技术限制。

    以这种方式使用瞬态写入模型的主要原因之一是使业务逻辑更改成本低廉且易于部署。

    建议对 Kafka 使用每个类型的主题,但这需要为该类型的每个实体加载事件,以便获取单个实体的事件。由于您无法通过日志位置判断哪些事件属于哪个实体。即使使用Snapshots 从已知的日志位置开始,如果需要对快照进行结构更改以支持逻辑更改,这可能会产生大量事件。

    缺乏冲突检测

    其次,由于针对同一实体的并发请求,用户可以创建竞争条件。保存冲突事件并在事后解决它们可能是非常不可取的。因此,能够防止冲突事件非常重要。为了扩展请求负载,通常使用无状态服务,同时使用条件写入来防止写入冲突(仅在最后一个实体事件是 #x 时才写入)。又名乐观并发。 Kafka 不支持乐观并发。即使它在主题级别支持它,它也需要一直到实体级别才能有效。要使用 Kafka 并防止发生冲突事件,您需要在应用程序级别使用有状态的序列化写入器(每个“分片”或任何 Kafka 的等价物)。这是一个重要的架构要求/限制。

    奖励原因:解决问题

    于 2021 年 9 月 29 日添加

    Kafka 旨在解决大规模数据问题,因此需要相应的开销。应用程序控制的事实来源是一种规模较小的深度解决方案。使用事件溯源以获得良好效果需要精心制作事件和流以匹配业务流程。这通常比通常对系统的其他部分有用的详细程度要高得多。考虑一下您的银行对账单是否包含银行内部流程每个步骤的条目。在确认到您的帐户之前,单笔交易可能有多个条目。

    当我问自己与 OP 相同的问题时,我想知道 Kafka 是否是事件溯源的扩展选项。但也许更好的问题是我的事件源解决方案大规模运行是否有意义。我不能对每一个案例都说话,但我认为通常情况并非如此。当这个尺度进入画面时,事件的粒度往往会有所不同。而且我的事件源系统可能应该将更高粒度的事件发布到 Kafka 集群,而不是将其用作存储。

    事件溯源仍然需要规模。策略因原因而异。事件流通常具有“完成”状态,如果存储或卷是问题,则可以存档。分片是另一种选择,特别适用于区域或租户隔离的场景。在不太孤立的场景中,当流以可以跨越分片边界的方式任意关联时,分片事件仍然很容易(按流 ID 分区)。但是对于事件消费者来说事情变得更加复杂,因为事件来自不同的分片并且不再是完全有序的。例如,您可以在接收描述所涉及帐户的事件之前接收交易事件。 Kafka 也有同样的问题,因为事件只在主题中排序。理想情况下,您设计消费者以便不需要在流之间进行排序。否则,您将采用合并不同的源并按时间戳排序,如果时间戳相同,则使用任意的决胜局(如分片 ID)。服务器的时钟如何不同步变得很重要。

    总结

    Further information

    你能强迫 Kafka 为应用程序控制的事实来源工作吗?当然,如果您足够努力并足够深入地整合。但这是个好主意吗?没有。


    每条评论更新

    评论已被删除,但问题类似于:那么人们使用什么来存储事件?

    似乎大多数人在现有数据库之上推出了自己的事件存储实现。对于非分布式场景,如内部后端或独立产品,well-documented 如何创建基于 SQL 的事件存储。在各种数据库之上还有可用的库。还有EventStore,就是为此而建的。

    在分布式场景中,我见过几个不同的实现。 Jet 的Panther project uses Azure CosmosDB,具有更改提要功能以通知听众。我在 AWS 上听说的另一个类似实现是使用 DynamoDB 及其 Streams 功能来通知侦听器。分区键可能应该是最佳数据分布的流 id(以减少过度配置的数量)。但是,在 Dynamo 中跨流的完整回放是昂贵的(读取和成本方面)。所以这个 impl 也是为 Dynamo Streams 设置的,用于将事件转储到 S3。当新的监听器上线,或者现有的监听器想要完整回放时,它会先读取 S3 以赶上。

    我当前的项目是一个多租户场景,我在 Postgres 之上推出了自己的项目。 Citus 之类的东西似乎适合可扩展性,按 tentant+stream 进行分区。

    Kafka 在分布式场景中还是很有用的。将每个服务的事件暴露给其他服务是一个不小的问题。事件存储通常不是为此而构建的,但这正是 Kafka 擅长的。每个服务都有自己的内部事实来源(可能是事件存储或其他),但听 Kafka 以了解“外部”发生的事情。该服务还可以向 Kafka 发布事件,以告知“外部”该服务所做的有趣事情。

    【讨论】:

    • @Dominik 我在更新部分(第 2 段)中提到了 EventStore。我会回去链接它。我试过了,它的性能令人印象深刻。对于我们的小团队来说,暂时不引入另一个数据库被认为更重要,因此 Postgres(也用于视图)。我们有可能在未来或未来的产品中转移到 EventStore。
    • @KaseySpeakman 主题与分区不同。一个主题有一个或多个分区。分区保证在任何给定时刻每个组只有一个消费者。以利用这一点的方式对您的实体进行分区。您不需要每个实体的主题,甚至不需要每个实体的分区。您只需对它们进行分区,以确保所有针对同一实体的命令都进入同一分区。
    • @KaseySpeakman 许多实体可以共享一个分区。谁说你总是必须通过重放事件直接从事件存储中加载实体的状态?在不严格遵循 Greg Young 的逐行实现的情况下,还有其他方法可以实现相同的概念。
    • @AndrewLarsson 如果您不按实体进行分区,那么您将如何防止实体级别的冲突事件?由于我们已经回到了并发冲突的完整循环,那么也许您应该在媒体上发布您自己的文章,或者关于您如何在生产中使用 Kafka 进行事件源(而不是流处理)的文章。您如何通过按类型分区和没有实体级并发控制来完成它。我会读它,如果我不同意,我什至不会在 cmets 上拖你。
    • @KaseySpeakman 以这种方式使用 Kafka 无论如何都不容易。但是,如果您处于认真考虑 CQRS 和事件溯源的规模,那么您处于无法以简单的方式做事的规模。你的并发模型对你的规模有直接的影响——不要随意选择一个。此外,HTTP 不是一种可靠的传输方式,同样,如果您处于这种规模,您将无法花时间解决丢失和/或重复的消息问题。这一切都可以通过在客户端和命令处理器之间使用 Kafka 来解决,但是是的,这是以复杂性为代价的。
    【解决方案6】:

    是的,Kafka 在事件溯源模型(特别是 CQRS)中运行良好,但是您在为主题设置 TTL 时要小心,并始终牢记 Kafka 不是为这种模型设计的,但我们可以很好地使用它。

    【讨论】:

    • Kafka 实际上是“为这种类型的使用而设计的”,如此处所述:confluent.io/blog/okay-store-data-apache-kafka;使用 Kafka 作为事件源的事件存储是本文的第一个用例。他们还说 NYT 为他们的文章数据做这件事。
    【解决方案7】:

    我认为你应该看看 axon 框架以及它们对 Kafka 的支持

    【讨论】:

      猜你喜欢
      • 2017-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-01
      • 2020-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多