Apache Kafka 通常被解释为一组概念:主题(topic)、分区(partition)、broker、生产者(producer)、消费者(consumer)、副本(replica)、偏移量(offset)、leader、follower 以及消费者组(consumer group)。

在入门阶段,这种讲法效果很好。

直到第一次故障进入了讨论。

一个 broker 宕机了。一个 follower 开始滞后。ISR 收缩了。一个生产者使用了 acks=all。一个消费者仍在持续读取,但只能读到高水位线为止。发生了一次 controller 选举。一个区域变得不可用。突然之间,这个系统不再是一张静态的图。它变成了一连串决策构成的时间线。

而这正是 Kafka 变得难以讲授的地方。

并不是因为单个概念本身有多难,而是因为有趣的行为只在它们彼此交互时才会显现。

这就是我们构建 Kafka 模拟器 的原因——一个基于浏览器的、确定性的 Kafka 模型,你可以安全地把它弄坏,并逐步回放。它基于 Apache Kafka 4.3 的语义运行,无需任何后端,也不会就你的场景发送任何遥测数据。

Kafka 的故障很难在白板上讲清楚

有些 Kafka 问题提出来很容易,但若没有可视化,回答起来却出人意料地难。

  • 当副本因子为 3min.insync.replicas2,且一个 broker 宕机时,会发生什么?
  • 当第二个 broker 也宕机时,又会发生什么变化?
  • 为什么在第一次故障后生产者仍能写入,但在第二次故障后却开始收到 NotEnoughReplicas
  • acks=all 究竟在等待什么?
  • 为什么高水位线停止前进了?
  • 在一个 broker 故障后,哪个副本会成为 leader?
  • 一次非纯净(unclean)leader 选举实际上会丢失什么?
  • 你如何解释一个健康的集群、一个降级的集群,以及一个仍然存活但已无法满足其持久性保证的集群之间的区别?

正是在这些时刻,一张静态图开始崩溃失效。

Kafka 是一个分布式系统。它有时间、有顺序、有故障、有恢复,也有各种取舍。最重要的经验往往隐藏在状态转换之中:故障前后、再均衡(rebalance)前后、controller 选举前后、ISR 变化前后。

一个用来看见 Kafka「动起来」的模拟器

模拟器的目标很简单:让 Kafka 的行为变得可见。

你可以更改 Kafka 设置、运行某个场景或构建自己的集群、把它弄坏,然后逐步检查发生了什么。模拟器不会让你从「健康」直接跳到「故障」,而是把中间的整条时间线暴露出来。

  • 你可以暂停场景。
  • 你可以向前或向后单步,或拖动到时间线上的任意时刻。
  • 你可以检查 broker、分区、副本、生产者、消费者、偏移量、ISR、高水位线以及各项指标。
  • 你可以打开 Why 标签页,阅读对当前状态的通俗语言解释。
  • 你可以打开 Metrics 标签页,看看在该情形下哪些 Kafka 指标会发生变化。
生产者的 Inspector 面板:acks 设为 all、开启幂等性、重试次数、重试退避、linger.ms、压缩,以及一个分区器选择器。
在模拟器中检查任意实体——这里是一个生产者:acks、幂等性、批处理、重试以及分区器,全部都可编辑。

这对于讲授故障行为尤其有用。在真实的 Kafka 集群中,故障是嘈杂的、并发的,而且往往难以隔离。在模拟器中,同样的故障变成了一个可控的学习时刻。

你可以问:「这个 produce 请求为什么失败了?」

  • 然后向后退一个事件。
  • 然后向前进一个事件。
  • 然后检查 ISR。
  • 然后查看高水位线。
  • 然后将生产者配置与当前的副本状态进行对比。

重点不仅仅在于展示最终结果。重点在于让通往那个结果的路径变得可理解。

经典示例:acks=allmin.insync.replicas

最简单也最有用的演练之一,同时也是最好的教学示例之一。它就是模拟器主页上的经典演示,你可以在自由演练(free-play)沙盒中亲手复现它。

从以下设置开始:

  • 副本因子:3
  • min.insync.replicas2
  • 生产者 acksall
一个名为 orders 的主题的配置面板,分区数为 3、副本因子为 3、min.insync.replicas 为 2。
经典配置:一个副本因子为 3、min.insync.replicas 为 2 的主题。

在一个健康的集群中,生产者写入 leader,follower 复制该记录,高水位线前进,记录随之被提交。

现在杀掉一个 broker。

集群处于降级状态,但仍可写入。仍然有两个同步副本,因此生产者可以满足 acks=all。这是一个重要的边界:系统不再完全健康,但它仍然能够保持所配置的持久性保证。

现在再杀掉一个 broker。

只剩下一个同步副本。leader 也许仍然存活,但生产者已无法再满足 min.insync.replicas=2。写入会失败并返回 NotEnoughReplicas

一个单数据中心集群,broker-1 和 broker-2 已宕机。broker-3 是幸存的 leader,生产者因 ISR 已跌至 min.insync.replicas 以下而卡在不断重试。
三个 broker 中有两个宕机。幸存的 leader 仍然保有数据,但由于 ISR 低于 min.insync.replicas,acks=all 的生产者只能不停地重试。

这个区别正是 Kafka 可靠性的核心经验之一。

一个集群可以是可用的。 一个 leader 可以是存在的。 一个主题里仍然可以有数据。 但写入仍然可能被拒绝,因为持久性契约无法被满足。

正是这类概念,在你能够把 ISR、leader、生产者请求、高水位线和指标变化一并显示在同一个屏幕上时,会变得容易理解得多。

为逐步学习而构建

模拟器中的每个场景都被设计成一个可导航的沙盒。

你不是在观看一段播放完就消失的固定动画。你可以像使用调试器一样在场景中穿梭。

Steps 标签页显示一个被拆分为编号步骤的场景,每个步骤都附有简短说明,点击即可移动播放头。
每个场景都是一段可导航的、逐步演练——点击任意步骤即可把播放头跳转到那里。

每个事件都是确定性的、带种子(seed)的时间线的一部分。你可以回放它、暂停它、向前单步、向后单步,并在每个时刻检查状态。这使它不仅适用于演示,也适用于工作坊、入职培训、调试讨论和架构评审。

完整的场景状态都被编码在 URL 中:场景、集群配置、你执行的每一个动作、种子,以及你在时间线上所处的位置。这意味着一个场景可以作为一个可复现的链接来分享——相同的配置、相同的种子、相同的时间线、相同的故障时刻。

这让模拟器非常适合用于这样的讲解:

「打开这个链接,跳到 broker 2 宕机的那个时刻。」

「现在检查 ISR。」

「现在向前走一步,观察 leader 选举。」

「现在看看生产者的报错。」

「现在把它和指标的变动对比一下。」

你无需再凭记忆描述 Kafka 的行为,而是可以指向一个具体的、可检查的状态。

首个 1.0 版本包含什么

对于首个 1.0 版本,我们以一个聚焦的、单数据中心(single-DC)版本作为起点,主题为 Fundamentals(基础)。

这个版本专注于 Kafka 的基础学习:主题、分区、偏移量、键与分区、broker、副本与 leader、日志末端偏移量(log end offset)与高水位线之间的区别、生产者确认(acks=0acks=1、acks 的权衡,以及 acks=1 的持久性缺口)、消费者拉取循环、分区在组成员之间的分配,以及再均衡。

它以十三个引导式场景的形式发布,每个场景都附带一条冻结的黄金轨迹(golden trace),外加一个自由演练沙盒,你可以在其中构建自己的单数据中心集群并做实验——包括上面那个 acks=all 持久性演练。

首个版本的目标并不是把我们内部拥有的每一个场景都暴露出来。目标是发布一个稳定、易懂的演练场,把核心机制讲透彻。

这意味着首个公开版本有意做得比其背后的模拟器引擎更小。我们宁愿先发布一组能够清晰解释 Kafka 的可靠场景,也不愿在解释、边界情况和视觉状态尚未就绪之前,就把每一个高级模式都发布出去。

场景库被分组为若干类别——Anatomy、Producers and EOS、Consumer groups、Replication、Tiered storage、Disaster recovery、Schema Registry、Authorization 以及 Share groups——每个类别都带有场景数量。
模拟器背后的完整场景库。1.0 版本发布的是 Fundamentals(基础);其余场景包大约每两周推出一次。

接下来会有什么

模拟器引擎所建模的内容,已经远多于第一个场景包所暴露的部分,而新的场景包大约每两周发布一次。更新日志 记录了已发布的内容以及接下来的计划。

即将推出的场景包将新增以下引导式场景:复制(replication)与 min.insync.replicas 边界、投递语义与事务、存储与生命周期、controller 与配额,以及一个混沌与故障实验室,还有多数据中心(multi-DC)灾难恢复——主备(active-passive)、双活(active-active)、跨 3 数据中心(stretched 3-DC)和 2.5 数据中心(2.5-DC)集群、数据中心故障转移、observer 提升、网络分区、慢 broker,以及非纯净 leader 选举。

这些场景很强大,但也需要谨慎对待。多数据中心的 Kafka 行为充满了各种取舍。很容易做出一个看起来令人印象深刻、却传达了错误经验的演示。我们希望这些高级场景是扎实的、可解释的,并且对其所做的假设保持诚实——这也是它们逐步推出、而非一次性全部上线的原因。

一个跨 2.5 数据中心的拓扑:数据中心 dc-a 已宕机,dc-b 处于活动状态并持有 leader,dc-c 是一个见证者(witness)。幸存数据中心中的一个 observer 正在被提升。
即将推出:一个跨 2.5 数据中心的集群在丢失一个数据中心之后——幸存一侧的 observer 被提升,以保持该分区可写。

我们想要讲清楚的故障模式

Kafka 的可靠性并不是单一的某项功能。它是一组取舍。

模拟器旨在通过具体的故障模式来帮助解释这些取舍:

  • broker 故障
  • 慢 follower
  • 网络分区
  • ISR 收缩
  • leader 选举
  • 非纯净 leader 选举
  • 生产者重试行为
  • 消费者位置与滞后
  • 数据中心故障转移
  • observer 提升
  • 复制滞后
  • 故障后的恢复

其中一些在首个版本中已经可以探索——消费者位置与滞后、acks=1 的持久性缺口,以及在自由演练沙盒中亲手制造的 broker 故障。其余的将随混沌场景包和多数据中心场景包一并到来。

Failure Lab 面板,带有用于分区或摧毁整个数据中心、以及杀掉 KRaft 仲裁(quorum)的控件。
即将推出:Failure Lab——隔离或摧毁一整个数据中心,或者杀掉 KRaft 仲裁,并观察集群如何反应。

重要的是,每个故障都应回答同样的几个教学问题:

  • 什么发生了变化?
  • 它为什么会变化?
  • 还有什么是安全的?
  • 还有什么已不再被保证?
  • 哪个指标应该告诉你出问题了?

一个好的模拟器不应只是显示红色图标。它应该解释这些图标背后的系统状态。

Why 标签页

模拟器最重要的部分之一是 Why 标签页。

当一个场景到达某个有趣的状态时,模拟器会解释集群为何会有这样的行为。

例如,在一次 broker 故障后,可视化界面可能显示生产者仍能够写入。Why 标签页会解释,ISR 中仍然包含足够的副本来满足 min.insync.replicas

在第二次故障之后,生产者可能开始收到 NotEnoughReplicas。Why 标签页会解释,acks=all 要求达到所配置的最小同步副本数量,而当前的 ISR 已经太小了。它还会直接为你指出导致这一情况的分区或 broker。

Why 标签页解释说有三个分区低于 min.insync.replicas,因此 acks=all 写入被以 NOT_ENOUGH_REPLICAS 拒绝,并附带一条建议的修复措施。
一次被拒绝写入上的 Why 标签页:有三个分区低于 min.insync.replicas,因此 acks=all 正以 NOT_ENOUGH_REPLICAS 失败——并且它会建议如何恢复。

这把一次故障从一个视觉事件,变成了一个学习事件。

目标不只是说「这失败了」。 目标是说「这之所以失败,是因为这项保证已无法再被满足」。

指标应该讲述同样的故事

模拟器还包含一个 Metrics 标签页,因为在生产环境中,Kafka 的问题通常是通过指标来诊断的。

当一个 follower 落后时,你会在 ISR 健康度和欠复制分区(under-replicated-partitions)的读数中看到它。当 acks=all 写入开始重试时,重试计数会变动,produce 吞吐量会下降。当集群恢复时,这些读数又会重新稳定下来。每个指标都会链接回最近一次使它变动的事件,因此你可以把一个数字与它发生变化的那一刻关联起来。

模拟器中的指标值是用于教学的,并不能替代生产环境中的测量。它们旨在方向上正确,并与场景状态绑定,从而让学习者能够将他们在集群中所见的现象,与他们在真实环境中会监控的那类信号联系起来。

这很重要,因为 Kafka 的学习常常把架构与运维割裂开来。模拟器试图把它们重新连接起来。

一次 broker 故障不仅仅是一个 broker 图标变红。它同时也是 ISR 收缩、欠复制分区、可能发生的 leader 选举、生产者行为的变化,以及指标的变动。

诚实的模拟,而非魔法

我们希望模拟器是有用的,但我们也希望它是诚实的。

它并不在浏览器中运行一个真实的 Kafka 集群。它不模拟操作系统调度、磁盘 I/O、页缓存行为、GC 停顿、真实的网络缓冲区、TLS 握手,或字节级精确的序列化。

它是一个确定性的、用于教学的 Kafka 行为模型。它被构建用来解释顺序、状态转换、故障后果以及配置取舍。它并不是被构建用来预测精确的吞吐量、延迟或生产性能的。

这个区别很重要。当一个模拟器帮助你建立正确的心智模型时,它是有价值的。而当它假装自己比实际更精确时,它就变得危险了。

因此,模拟器包含了一个明确的模型局限性页面。它解释了什么被建模、什么被近似,以及什么被略过。

帮助我们把它做得更好

我们还创建了一个公开仓库,用于报告 bug 和不正确的行为。

这很重要,因为 Kafka 充满了边界情况,而模拟的 bug 就是教学的 bug。如果某个场景呈现了错误的解释、错误的状态转换或错误的故障结果,我们希望知道。

模拟器将在那些以不同方式使用 Kafka 的人的反馈中改进得最快:平台团队、开发者、SRE、培训师、咨询师,以及任何曾经不得不去解释一个 Kafka 集群为何表现得与预期不同的人。

如果有什么看起来不对劲,请向我们报告。

从单数据中心演练场开始

首个版本是一块基石:一个基于浏览器、聚焦于单数据中心学习的 Kafka 模拟器。它专为安全实验而设计。无需任何后端。不需要真实的集群。你可以随意把东西弄坏、回放场景、分享 URL,并检查每一个步骤。

自由演练的集群设置面板:broker 数量、KRaft 选民(voters)、控制平面、机架(racks),以及一个机架感知放置开关。
自由演练的集群设置——broker、KRaft 选民、机架,以及机架感知放置。

多数据中心和灾难恢复场景将在接下来推出,包括主备、双活、跨 3 数据中心、跨 2.5 数据中心、数据中心故障转移,以及 observer 提升。

眼下,先从基础开始。打开演练场,启动一个单数据中心的自由演练集群,然后:

设置 replication.factor=3

设置 min.insync.replicas=2

设置 acks=all

杀掉一个 broker。

然后再杀掉一个。

当你能看见 Kafka 动起来时,它就更容易理解了。

当你能安全地把它弄坏时,它就更容易理解了。