我会一直回到这个 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 发布事件,以告知“外部”该服务所做的有趣事情。