如果我们从头重建 Kafka 会怎样?
Gunnar Morling
关于软件工程的随机思考
Gunnar Morling
关于软件工程的随机思考
如果我们从头重建 Kafka 会怎样?
发布于 2025 年 4 月 24 日
最近几天,我花了一些时间研究最近发布的 KIP-1150 ("Diskless Kafka"),以及 AutoMQ’s Kafka fork,它将 Apache Kafka 与对象存储(如 S3)紧密集成。 遵循 WarpStream 设定的示例,这些项目旨在显着改善在云环境中使用 Kafka 的体验,提供更好的弹性,大幅降低成本,并为原生 lakehouse 集成铺平道路。
这让我开始思考,如果我们要从头开始开发一个持久的云原生事件日志——可以称之为 Kafka.next——它应该具有哪些特征和特性? 分离存储和计算以及支持对象存储将是基本要求,但还应该有什么? 多年来,我一直使用 Kafka 来构建事件驱动的应用程序以及运行实时 ETL 和变更数据捕获管道,以下是我个人的愿望清单:
-
抛弃分区: 当数据存储在节点本地磁盘上时,topic partitions 对于扩展目的至关重要,但在云中将数据存储在实际上无限大的对象存储上时,它们不是必需的。 虽然分区也提供排序保证,但我从未觉得从客户端的角度来看这非常有用。 你要么希望给定 topic 上的所有消息都具有全局排序,要么(更常见的是)希望所有具有相同 key 的消息都具有排序。 相比之下,对其他不相关的消息(其 key 在哈希后恰好产生相同的分区)进行定义排序的价值不大,因此没有太多意义将分区作为概念向用户公开。
-
以 Key 为中心的访问: 除了基于分区的访问之外,高效地访问和重放所有具有相同 key 的消息将是理想的。 与其粗略地扫描给定 topic 或分区上的所有记录,不如拥有数百万个实体级别的流! 这不仅可以让你精确地访问所需的数据子集,还可以让你根据需求动态地增加和减少消费者的数量,而不会达到预定义分区数的限制。 具有保证排序的 Key 级别流将成为 Event Sourcing 架构以及基于 actor 和 agentic 的系统的完美基础。 此外,这种方法在很大程度上解决了基于分区的系统中使用累积确认的 head-of-line blocking 问题:如果消费者无法处理特定消息,这将只会阻止具有相同 key 的其他消息(通常这正是你想要的),而所有其他消息都不会受到影响。 与其使用粗粒度的分区,不如将单个消息 key 作为故障域。
-
Topic 层次结构: 在诸如 Solace 等系统中可用,topic 层次结构将消息 payload 的各个部分提升为结构化的类似路径的 topic 标识符,从而允许客户端以有效的方式订阅所有可用流的任意子集,而无需代理反序列化和解析整个消息。
-
并发控制手段: 目前,使用 Kafka 作为记录系统可能存在问题,因为你无法阻止写入基于存储数据的过时视图的消息。 并发控制(例如通过消息 key 的乐观锁定)将有助于检测和隔离并发冲突的写入。 这样,当消息成功得到确认时,就可以保证它是在看到该 key 的最新状态后生成的,从而避免了丢失的更新。
-
Broker 端 schema 支持: Kafka 将消息视为具有任意内容的不透明字节数组,需要将消息 schema 带外传播给消费者。 当错误的(或恶意的)生产者发送不符合规范的数据时,这尤其成问题。 此外,如果没有其他工具,当前的架构会阻止将 Kafka 数据写入开放表格式(如 Apache Iceberg)。 由于所有这些原因,Kafka 大多数时候都与 schema registry 一起使用,但将 schema 支持作为一等公民概念将允许更好的用户体验——例如,Kafka 可以开箱即用地公开 AsyncAPI-compatible metadata —— 并且还可以打开以不同方式存储数据的大门,例如以 columnar 表示形式。
-
可扩展性和可插拔性: 许多成功的开源项目(如 Postgres 或 Kubernetes)的一个共同特征是它们的可扩展性。 用户和集成者可以通过提供定义良好的扩展点和插件合约的实现来自定义系统的行为,而不是修改系统本身的核心(遵循 Open-closed principle)。 这将支持例如自定义 broker 端的消息过滤器和转换(解决当前需要协议感知代理的许多场景,例如 Kroxylicious)、存储格式(例如 columnar)等等。 速率限制、topic 加密或通过 Iceberg 表支持 topic 等功能应该可以通过系统的扩展来实现。
-
同步提交回调: 端到端 Kafka 管道确保最终一致性。 当将记录生成到 topic 然后使用该记录在某些下游数据存储上实现某些派生数据视图时,生产者无法知道何时能够“看到”该下游更新。 对于某些用例,如果能够保证在确认生成请求时派生数据视图已更新,这将非常有用,从而允许 Kafka 充当具有强大的 read-your-own-writes 语义的真正数据库的日志。
-
Snapshotting: 目前,Kafka 支持 topic compaction,它只会保留给定 key 的最后一条记录。 如果记录包含它们所代表的实体的完整状态(客户、采购订单等),这很有效。 但对于部分或增量事件不起作用,这些事件描述了对实体的更改,需要一个接一个地应用才能完全恢复实体的状态。 假设支持高效的基于 key 的消息重放(见上文),这将花费越来越长的时间,因为 key 的记录数量会增加。 内置的 snapshot 支持可以实现“逻辑 compaction”,将 key 的所有事件传递给一些事件处理程序,该处理程序将它们压缩为 snapshot。 然后,这将作为后续更新事件的基础,同时可以在 compaction 期间删除该 key 的所有先前记录。
-
多租户: 任何现代数据系统都应该从一开始就以多租户为中心构建。 启动一个新的特定于客户的环境应该是一个非常廉价的操作,瞬间发生; 个别租户的工作负载应严格隔离,在访问控制和安全性、资源利用率、计量等方面互不干扰。
其中一些功能已经在其他系统中得到支持——例如,S2 中的 high cardinality streams、Waltz 中的 optimistic locking 或 Apache Pulsar 中的 multi-tenancy。 但其他的则不然,而且我不知道有哪个系统,更不用说开源的系统,会结合所有这些特性。
现在,这描述了我个人的(也就是说,绝不应将这篇文章理解为以任何官方身份代表我的雇主 Confluent 发言)对 Kafka.next 可能是什么以及它可以提供的语义的愿望清单,这是由我看到人们希望使用 Kafka 的用例和应用程序驱动的。 但我确信每个使用 Kafka 或类似平台工作过一段时间的人都会对此有自己的想法,我很乐意在评论中了解您的想法!
最后,一个重要的问题当然是这样的系统实际上会如何架构? 虽然我不得不把这个答案留到以后再回答,但可以肯定地说,在这种系统之上构建一个 log-structured merge (LSM) tree 将是一个可能的选择。
© 2019 - 2025 Gunnar Morling | Licensed Under Creative Commons BY-SA 4.0