我一直在关注这个问题。但我发现现有的答案不够细致,所以我添加了这个。
简而言之,取决于您的事件溯源用法。
据我所知,有两种主要类型的事件溯源系统。
下游事件处理器 = 是
在这种系统中,事件在现实世界中发生并记录为事实。例如,仓库系统用于跟踪产品托盘。基本上没有冲突的事件。即使是错误的,一切都已经发生了。(例如:托盘123456放在卡车A上,但计划放在卡车B上。)然后稍后通过报告机制检查异常情况。Kafka似乎非常适合这种下游事件处理应用程序。
在这种情况下,可以理解为什么Kafka的人们正在将其推广为事件溯源解决方案。因为它与例如点击流中已经使用的方式非常相似。但是,使用术语“事件溯源”(而不是流处理)的人们可能指的是第二种用法...
应用程序控制的真相来源 = 否
这种应用程序声明自己的事件是由用户请求通过业务逻辑产生的结果。Kafka在这种情况下不起作用,有两个主要原因。
实体隔离的缺乏
这种情况需要能够为特定实体加载事件流。这样做在Kafka中是不切实际的。使用每个实体的主题可以允许此操作,但是当可能存在数千或数百万个实体时,这是行不通的。这是由于Kafka / Zookeeper的技术限制造成的。
使用短暂的写入模型的主要原因之一是使业务逻辑更改变得便宜且易于部署。
对于Kafka,建议使用按类型分主题,但这将要求为该类型的每个实体加载事件,以获取单个实体的事件。由于您无法通过日志位置确定哪些事件属于哪个实体,即使使用快照从已知的日志位置开始,如果需要结构更改来支持逻辑更改,则需要处理大量事件。
缺乏冲突检测
其次,用户可以创建竞争条件,因为对同一实体进行并发请求。保存冲突事件并在事后解决可能是非常不希望的。因此,重要的是能够防止冲突事件。为了扩展请求负载,通常会使用无状态服务,同时使用条件写入(仅在上一个实体事件为#x时才写入)。也称为乐观并发。Kafka不支持乐观并发。即使它在主题级别支持它,它也需要一直到实体级别才能有效。为了使用Kafka并防止冲突事件,您需要在应用程序级别使用有状态的序列化编写器(每个“ shard”或Kafka等效项)。这是一个重要的架构要求/限制。
主要原因:适合问题
添加于2021/09/29
Kafka旨在解决大规模数据问题。应用程序控制的真相来源是一个小规模、深入的解决方案。有效使用事件源需要制定与业务流程匹配的事件和流。这通常比通常对于大规模消费者有用的细节级别要高得多。考虑一下,如果您的银行对每个银行内部交易过程的每个步骤都有一个条目,那会怎样。在确认到您的账户之前,单笔存款或提款可能会有很多条目。银行需要这种详细级别来处理交易。但对于您来说,它大多是晦涩难懂的银行术语(领域特定语言),无法用于调整您的账户。相反,银行为消费者发布单独的事件。这些是每个完成的交易的粗略摘要。这些摘要事件是消费者在其银行对账单上所知道的“交易”。
当我像OP一样问自己同样的问题时,我想知道Kafka是否是事件源缩放选项。但也许更好的问题是,我的事件源解决方案是否有意义在大规模运作。我不能谈论每种情况,但我认为通常不是这样。当这种规模进入图片时,就像银行对账单的例子一样,事件的粒度往往是不同的。我的事件源系统应该向Kafka集群发布粗略的事件以供大规模消费者使用,而不是将其用作内部存储。
对于事件溯源,仍然需要进行扩展。策略取决于原因。通常,事件流具有“完成”或“不再有用”的状态。如果事件大小/数量成为问题,则将这些流存档是一个好答案。分片是另一个选择——非常适合区域或租户隔离的场景。在较少隔离的情况下,当流以可以跨越分片边界的方式任意相关联时,分片仍然是移动(按流ID进行分区)。但是,在流之间没有排序保证,这可能会使事件消费者的工作更加困难。例如,消费者可能会在接收描述所涉及帐户的事件之前接收交易事件。第一反应是“只使用时间戳”来排序接收到的事件。但是仍然无法保证完美发生顺序。太多不可控因素。网络故障、时钟漂移、宇宙射线等。理想情况下,设计消费者不需要跨流依赖。拥有临时缺失数据的策略。就像数据的渐进增强一样。如果您确实需要数据不可用而不是不完整,请使用相同的策略。但要将不完整的数据放在单独的区域或标记为不可用,直到填充所有数据为止。您也可以尝试处理每个事件,知道它可能由于缺少先决条件而失败。将失败的事件放入重试队列中,处理下一个事件,并稍后重试失败的事件。但要注意毒消息(事件)。
摘要
你能强制Kafka为应用程序控制的真相来源工作吗?如果你足够努力并且集成得足够深入,那么当然可以。但是这是一个好主意吗?不是。
根据评论更新
评论已被删除,但问题大致是:人们用什么来存储事件?
看起来大多数人会在现有数据库的基础上自己实现事件存储。对于非分布式场景,例如内部后端或独立产品,如何创建基于SQL的事件存储是有充分文档支持的。还有一些库可以用于各种不同类型的数据库。也有EventStoreDB专为此目的而构建。
在分布式场景中,我见过几种不同的实现。Jet的Panther项目使用Azure CosmosDB,利用Change Feed功能通知监听器。我听说AWS上类似的实现是使用DynamoDB和其Streams功能来通知监听器。分区键可能应该是流ID,以获得最佳数据分布(以减少过度预配的数量)。然而,在Dynamo中跨流进行完整重放是昂贵的(无论是读取还是成本方面)。因此,这个实现也设置了Dynamo Streams将事件转储到S3中。当新的监听器上线或现有的监听器想要进行完整重放时,它会首先读取S3以进行追赶。
我的当前项目是一个多租户场景,我在Postgres之上自己实现了一个类似的事件存储。像Citus这样的东西似乎适合可扩展性,按租户+流进行分区。
Kafka在分布式场景中仍然非常有用。将每个服务的关键事件暴露给其他服务是一个不容易解决的问题。事件存储通常不是为此而构建的,但这正是Kafka擅长的。每个服务都有自己的内部真相来源(可能是事件、BNF、图形等),然后侦听Kafka以了解“外部”正在发生的事情。服务向Kafka发布公共事件,以通知外界它遇到的有趣事情。