使用Kafka作为(CQRS)事件存储。好主意吗?

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

Kafka支持的一个主要观点是:

  • 事件捕获/存储,当然是所有HA。
  • Pub / sub架构
  • 能够重放事件日志,允许新的订阅者注册到系统后的事实。

不可否认,我不是100%精通CQRS /事件源,但这似乎非常接近事件商店应该是什么。有趣的是:我真的找不到那么多关于Kafka被用作事件商店的信息,所以也许我错过了一些东西。

那么,卡夫卡还缺什么东西,让它成为一个好的事件商店呢?这会有用吗?使用它生产?对洞察力、链接等感兴趣。

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

92231 次浏览

Kafka是一个消息系统,它与事件存储有许多相似之处,但引用他们的介绍:

Kafka集群保留所有已发布的消息——不管它们是否 已被消耗-在可配置的时间段内。例如,如果 保留期设定为两天,然后是之后的两天 消息发布后,就可以使用了 将被丢弃以释放空间。卡夫卡的表演是有效的 常数的数据大小,所以保留大量的数据不是一个 问题。< / p >

因此,尽管消息可能被无限期地保留,但预期它们将被删除。这并不意味着您不能使用它作为事件存储,但使用其他东西可能会更好。看一下EventStoreDB作为替代。

更新

卡夫卡的文档:

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

更新2

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

我是卡夫卡的原作者之一。Kafka可以很好地作为事件来源的日志。它是容错的,可扩展到巨大的数据大小,并有一个内置的分区模型。

在LinkedIn上,我们将它用于该表单的几个用例。例如,我们的开源流处理系统Apache Samza自带内置支持用于事件源。

我认为你很少听说使用Kafka进行事件源,主要是因为事件源术语似乎在Kafka最流行的消费网络空间中并不流行。

我已经写了一些关于这种类型的Kafka用法在这里

你可以使用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并不是被设计为事件存储,而是同时作为高性能的代理和缓冲区来处理快速生产者和缓慢消费者的场景,例如。

请看最终结果。http://eventuate.io/ . io微服务开源框架来发现更多关于潜在问题

更新截止2018年2月8日

我没有从评论中加入新的信息,但同意其中一些方面。这次更新更多的是关于微服务事件驱动平台的一些建议。如果你认真考虑微服务的健壮设计和最高性能,我会给你一些你可能会感兴趣的提示。

    不要用Spring——它很棒(我自己也经常用),但同时又笨重又慢。它根本不是微服务平台。它“只是”一个帮助你实现的框架(这背后有很多工作..)。其他框架“只是”轻量级REST或JPA或不同重点的框架。我推荐可能是同类中最好的开源完整微服务平台,它回归到纯Java的根源: 李https://github.com/networknt < / >
如果您想了解性能,可以将自己与现有的基准测试套件进行比较。 https://github.com/networknt/microservices-framework-benchmark < / p >
  1. 完全不要用卡夫卡:-))半开玩笑。我的意思是,虽然Kafka很棒,但它是另一个以经纪人为中心的系统。我认为未来是没有经纪人的消息传递系统。你可能会感到惊讶,但是有比Kafka系统更快的系统:-),当然你必须降到更低的级别。看看《编年史》。

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

  3. 对于消息,尝试查看编年史队列,地图,引擎,网络。我的意思是摆脱这种老式的以经纪人为中心的强大>解决方案,使用微消息系统(嵌入式)。编年史队列实际上比Kafka还要快。但我同意这不是一个解决方案,你需要做一些开发,否则你就去买企业版(付费版)。最后,从Chronicle构建自己的消息层的努力将通过消除维护Kafka集群的负担来获得回报。

我一直在思考这个QA问题。我觉得现有的答案不够细致,所以我加上了这个。

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

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

下游事件处理器= Yes

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

在这种情况下,Kafka的人提倡将其作为事件源解决方案是可以理解的。因为它非常类似于它已经在点击流中使用的方式。然而,人们使用术语事件来源(相对于流处理)可能指的是第二种用法……

应用程序控制的真相来源=否

这类应用程序在用户请求通过业务逻辑时声明自己的事件。Kafka在这种情况下不太适用,主要有两个原因。

缺乏实体隔离

此场景需要为特定实体加载事件流的能力。这样做的常见原因是为用于处理请求的业务逻辑构建一个瞬态写模型。这样做在卡夫卡中是不切实际的。使用每个实体主题可以实现这一点,但是当存在数千或数百万个实体时,这是不可能的。这是由于Kafka/Zookeeper的技术限制。

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

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

缺乏冲突检测

其次,用户可以由于对同一实体的并发请求而创建竞态条件。保存冲突事件并在事后解决它们可能是非常不可取的。因此,预防冲突事件是很重要的。为了扩展请求负载,通常使用无状态服务,同时使用条件写来防止写冲突(仅当最后一个实体事件是#x时才写)。也就是乐观并发。Kafka不支持乐观并发。即使它在主题级别支持它,它也需要一直到实体级别才能有效。为了使用Kafka并防止冲突事件,你需要使用一个有状态的、序列化的写入器(per "shard"或者任何Kafka的对等物)在应用级别。这是一个重要的体系结构需求/限制。

主要原因:设备存在问题

添加2021/09/29

Kafka旨在解决大规模数据问题。应用程序控制的真相来源是一个规模更小、更深入的解决方案。使用事件源获得良好效果需要精心制作事件和流以匹配业务流程。这通常具有比大规模消费者通常有用的更高级别的细节。考虑一下您的银行对账单是否包含银行内部交易流程的每一步。在确认到您的帐户之前,一笔存款或取款可能有许多分录。银行需要这种级别的详细信息来处理交易。但对您来说,它主要是难以理解的银行行话(特定于领域的语言),无法用于对帐。相反,银行为消费者单独发布活动。这些是每个已完成事务的粗粒度摘要。这些汇总事件就是消费者所知的“交易”。在他们的银行账单上。

当我问自己和OP同样的问题时,我想知道Kafka是否是事件来源的可伸缩选项。但也许更好的问题是,我的事件源解决方案大规模运行是否有意义。我不能谈到每一个案例,但我认为通常情况下并非如此。当这个尺度出现时,就像银行对账单的例子一样,事件的粒度往往是不同的。我的事件源系统可能应该将粗粒度事件发布到Kafka集群,以提供大规模消费者,而不是将Kafka用作内部存储。

事件来源仍然需要规模。不同的策略取决于原因。事件流通常有一个&;done&;或“;no-longer-useful"状态。如果事件大小/容量有问题,那么存档这些流是一个很好的答案。分片是另一种选择——非常适合区域隔离或租户隔离的场景。在不那么孤立的场景中,当流以一种可以跨分片边界的方式任意关联时,分片仍然是移动(按流ID分区)。但是没有跨流的顺序保证,这可能会使事件使用者的工作更加困难。例如,使用者可以在接收描述所涉及的帐户的事件之前接收事务事件。第一直觉是“只使用时间戳”。对收到的事件进行排序。但仍不能保证完美的发生顺序。太多不可控因素。网络打嗝,时钟漂移,宇宙射线等。理想情况下,应该将使用者设计为不需要跨流依赖。为暂时丢失的数据制定策略。比如数据的渐进式增强。如果您确实希望数据不可用,而不是不完整,请使用相同的策略。但是将不完整的数据保存在一个单独的区域或标记为不可用,直到全部填写完毕。您也可以尝试处理每个事件,知道它可能会因为缺少先决条件而失败。将失败的事件放入重试队列,处理下一个事件,然后重试失败的事件。但是要注意有害消息(事件)。

总结

你能强迫卡夫卡为一个应用程序控制的真相来源工作吗?当然,如果你足够努力,足够深入地融入。但这是个好主意吗?不。


每条评论更新

该评论已被删除,但问题是这样的:人们用什么来存储事件?

似乎大多数人都将自己的事件存储实现放在现有数据库之上。对于非分布式场景,如内部后端或独立产品,如何创建基于sql的事件存储是证据确凿的。在各种各样的数据库之上还有很多可用的图书馆。还有EventStoreDB,它是为此目的而构建的。

在分布式场景中,我见过几种不同的实现。Jet的黑豹项目使用Azure CosmosDB,使用Change Feed功能通知侦听器。我在AWS上听说的另一个类似的实现是使用DynamoDB和它的Streams特性来通知侦听器。分区键可能应该是最佳数据分布的流id(以减少过度配置的数量)。然而,在Dynamo中跨流的完整回放是昂贵的(读取和成本方面)。因此,这个impl也是为Dynamo Streams设置的,以便将事件转储到S3。当一个新的侦听器上线时,或者现有的侦听器想要完整的重播时,它将首先读取S3以赶上进度。

我目前的项目是一个多租户场景,我在Postgres的基础上开发了自己的项目。像Citus这样的东西似乎适合于可伸缩性,按帐篷+流进行分区。

Kafka在分布式场景中仍然非常有用。将每个服务的关键事件公开给其他服务并不是一个简单的问题。事件存储通常不是为此而构建的,但这正是Kafka所擅长的。每个服务都有自己的内部真相来源(可以是事件、BNF、图表等),然后听Kafka来了解“外部”正在发生什么。该服务将公共事件发布给Kafka,以通知外界它遇到的有趣的事情。

是的,Kafka在事件源模型特别是CQRS中工作得很好,但是你在为主题设置ttl时要小心,并始终记住Kafka不是为这个模型设计的,但是我们可以很好地使用它。

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

所有现有的答案似乎都很全面,但有一个术语问题,我想在我的答案中解决这个问题。

什么是事件来源?

似乎如果你看五个不同的地方,你会得到这个问题的五个不同答案。

然而,如果你看看Greg Young从2010年开始的,它很好地总结了这个想法,从第32页开始,但它没有包含最终的定义,所以我敢自己制定它。

事件源是一种持久化状态的方法。不是由于状态突变而将一个状态替换为另一个状态,而是持久化表示该突变的事件。因此,您总是可以通过读取所有实体事件并按顺序应用这些状态变化来获得实体的当前状态。通过这样做,当前实体状态变成这个实体的所有事件的左折叠

“好”是什么意思?事件存储(数据库)?

任何持久性机制都需要执行两个基本操作:

  • 将新的实体状态保存到数据库中
  • 从数据库检索实体状态

这就是Greg谈论实体概念的地方,其中每个实体都有自己的事件流,由实体id唯一标识。当您有一个数据库,它能够通过实体id读取所有实体事件(读取流)时,使用Event Sourcing不是一个困难的问题。

Greg在论文中提到了CQRS上下文中的事件源,他解释了为什么这两个概念可以很好地相互配合。尽管您拥有一个充满了许多实体的原子状态变化的数据库,但是跨多个实体的当前状态进行查询是一项艰巨的工作。这个问题可以通过分离用作真相来源的事务(事件源)存储和用于跨多个实体的当前系统状态的报告和查询的报告(查询,读取)存储来解决。查询存储不包含任何事件,它包含多个实体的预计状态,根据查询数据的需要组合。它不一定需要包含每个实体的快照,您可以自由选择查询模型的形状和形式,只要您可以将事件投射到该模型即可。

因此,“适当的”;事件数据库将需要支持所谓的_real-time订阅,它将向查询模型交付新的(和历史的,如果我们需要重播)事件。

我们还知道,在决定允许的状态转换时,我们需要掌握实体状态。例如,已经执行的转账不应该执行两次。由于查询模型在定义上是陈旧的(即使是毫秒级的),当您对陈旧的数据做出决策时,它就变得危险了。因此,在对实体执行操作时,我们使用来自事务(事件)存储的最新且完全一致的状态来重建实体状态。

有时,您还希望从数据库中删除整个实体,这意味着删除其所有事件。例如,这可能是符合gdpr的要求。

那么,作为事件存储的数据库需要哪些属性才能使事件源系统正常工作呢?就几个:

  • 使用实体id作为键,将事件附加到有序的、只能追加的日志中
  • 使用实体id作为键,按顺序加载单个实体的所有事件
  • 删除给定实体的所有事件,使用实体id作为键
  • 支持实时订阅项目事件以查询模型

卡夫卡是什么?

Kafka是一个高度可伸缩的消息代理,基于仅追加日志。Kafka中的消息是根据主题生成的,现在一个主题通常包含一个单独的消息类型,以便更好地使用模式注册表。主题可以是类似cpu负载这样的东西,其中我们为许多服务器生成CPU负载的时间序列测量。

Kafka主题可以分区。分区允许并行地生成和使用消息。消息只在一个分区内排序,通常需要使用一个可预测的分区键,这样Kafka就可以跨分区分发消息。

现在,让我们看一下清单:

  • 你能把事件附加到Kafka吗?是的,它叫生产。你能用实体id作为键附加事件吗?不是真正的,因为分区键用于跨分区分发消息,所以它实际上只是一个分区键。在另一个答案中提到的一件事是乐观并发。如果你使用关系数据库,你可能会使用Version列。对于NoSQL数据库,您可能已经使用了文档eTag。两者都允许您确保您更新的实体处于您所知道的状态,并且它在操作期间没有发生变化。Kafka 为这种状态转换提供了任何支持乐观并发的东西。
  • 你能用实体id作为键从Kafka主题中读取单个实体的所有事件吗?不,你不能。因为Kafka不是一个数据库,它的主题上没有索引,所以从主题中检索消息的唯一方法就是消费它们。
  • 你能用实体id作为键从Kafka中删除事件吗?没有,不可能。消息只有在其保留期限到期后才会从主题中删除。
  • 你能订阅一个Kafka主题来按顺序接收实时(和历史)事件,这样你就可以将它们投射到你的查询模型中吗?是的,因为主题是分区的,你可以扩展你的投影来提高性能。

那么,为什么人们一直这样做呢?

我相信很多人认为Kafka是事件源系统的事件存储的好选择的原因是他们把事件源和简单的发布-订阅混淆了(你可以用一个炒作的词"EDA"或者事件驱动架构代替)。使用消息代理将事件展开到其他系统组件是几十年来众所周知的模式。“经典”的问题;代理,即消息一旦被消费就会消失,因此您不能构建类似于根据历史记录构建的查询模型之类的东西。另一个问题是,当投射事件时,您希望它们以与产生事件相同的顺序被消费,并且“经典”;代理通常旨在支持竞争消费者模式,该模式从定义上讲不支持有序消息处理。毫无疑问,Kafka 支持竞争的消费者,它有一个消费者每一个或多个分区的限制,但不是相反。Kafka很好地解决了排序问题和历史消息保留问题。所以,你现在可以从你通过Kafka推送的事件中构建查询模型。但这并不是事件来源的最初想法,它是我们今天所说的EDA。一旦明确了这种分离,我们就不会再看到任何只能追加的事件日志都是事件源系统的事件存储数据库的良好候选。