Let it Recover!
作者:Eric Fu, RisingWave Labs 内核开发工程师
许多用户问我们:RisingWave 如何容忍故障和保证高可用?这篇文章是对该问题的回应。
我的上一份工作是开发分布式 OLTP 数据库,当谈论起高可用、故障容忍这些 buzzword 的时候,我总会不由自主地联想到多副本、Paxos、WAL(Write-Ahead Log)等名词。或许你也一样如此?看来我们都是合格的系统设计者。
但是,今天我正想说的是,流计算真的与数据库有很大不同。流计算的全部意义在于计算,如果数据的流动停止了,那便失去了意义。在“高可用”这个主题下,数据库希望每一个写入的字节都被持久化而不丢失,而流计算的目标则是尽快恢复任务,让数据流水线再次流动起来。
不幸的是,我们无法用 Paxos 来保障流计算的高可用,而且也不值得为计算任务分配冗余的节点。那么,该怎么做呢?在揭晓答案之前,请允许我聊聊Apollo Guidance Computer 的故事。
1The Apollo Guidance Computer (AGC)
你可能在互联网上看过许多有关 AGC 的 Meme,比如:Your smartphone is millions of times more powerful than the Apollo 11 guidance computers。这是真的。下面的图的就是 AGC。这个大铁盒子重达 70 磅(32kg),每秒能够执行 43,000 条指令,用今天的话说,0.043 MHz,并配有手工打造的 2K word RAM 和 36K word ROM(word 长度为 15 比特)。今天的 iPhone 15 Pro 拥有 3.78GHz 6 核 CPU,大约是它的 53 万倍,而我们只用它玩原神。
但是受限于篇幅,今天我们将专注在它的软件或者说操作系统的设计上。这可能是世界上最著名也是最早的实时操作系统,它拥有
数码管用户界面(图中的黑白色盒子) 外围 IO 设备,包括控制按钮、各种雷达和传感器 为实时系统设计的调度器,基于优先队列,最大长度为 8 检查点机制,Restart on failure to recover——这是今天的主角
2将登月从失败中拯救的设计
AGC 的设计者认为,软件故障大多数是短暂性的。其实,不仅是 AGC、你的家庭路由器和各式各样的嵌入式设备,都面临同样的问题。网络抖动、磁盘坏道或是内存比特反转,这样的故障每天都在数据中心里发生。AGC 工作在宇宙空间中,更是如此。
与其期望每个异常都被正确处理,还不如重启整个程序,没错,这被无数次证明是最有效的方式。但是重启会丢失当前的状态,这就是为什么需要 checkpoint。程序在运行中,时常在存储中记录下“好的状态”,一旦发生故障,则重启并恢复到最后的检查点。
在 Apollo 11 号飞船的登月过程的刹车阶段(braking phase,Phase 63),AGC多次报 1201 和 1202 错误并短暂地停止工作。但所幸的是,最后飞船平稳降落在了月球上。这期间究竟发生了什么?
刹车阶段是计算最密集的阶段之一。AGC 通过导航算法不断修正飞船的姿态,从而平稳地落在月球上。这个程序消耗了 80% 的 CPU。这在预期之中。(图 a)
但意想不到的事情发生了。原本被设计为在返回阶段才会使用的 rendezvous rader 发生了硬件 bug,每秒向 AGC 发送 1000 次中断,但实际上没有输入任何信息。AGC 可怜的 CPU 被这个 bug 用掉了 15%,于是仅剩下 5% 的冗余。幸好,它依然能正常工作,没人知道雷达的问题。(图 b)
Buzz Aldrin,登月的宇航员之一,在屏幕上执行了 V16N68 号程序,这个程序用来监控登月的进程,包括显示当前离月球的距离、速度等,并且实时更新。这个程序大约消耗 10% 的 CPU。Buzz 在过去的模拟训练中已经重复了许多次这样的操作。(图 c)
但是,AGC 剩余的 5% CPU 不足以支撑这个程序,任务队列逐渐变得拥挤,直到新任务的入队请求超时,触发重启。Buzz 最不愿看到的事情发生了,PROG(program error)指示灯闪烁,紧接着,RESTART 指示灯亮起,AGC 重启了。(图 d&e)
幸运的是,重启后系统根据 checkpoint 恢复到 Phase 63 并继续执行引导程序。Buzz 手动运行的 V16N68 不在其中,毕竟它只是一个无关紧要的、无状态的监控 UI。CPU load 恢复到了 95%,可以继续正常工作。(图 a)
起初,Buzz 并不知道问题在哪里,于是他重复了上述过程几次,直到他感觉到,程序的异常似乎与他启动 V16N68 有关。当然,幸好在每一次尝试中,AGC 都正确的恢复了。这都要感谢 checkpoint-restart 机制!
3回到 2023 年
今天,相比于许多闪亮的新概念,checkpoint-restart 机制称得上默默无闻,甚至连一个权威的名字都没有。但是它的灵魂又存在于许多地方,例如 erlang 以及 actor 系统所提倡的 “let-it-crash”,只不过他们没有强调保存状态的重要性,而是期待开发者将状态放在数据库中。
我尝试 Google 了这个关键词,得到的结果涉及到 HPC、MPI、Simulation 等等,它们的共同点是,都是长时间的计算密集型任务,就像流计算一样。
据我所知,Apache Flink 是第一个引入 Checkpoint 的流计算系统。Apache Flink 创新性地将 Chandy-Lamport 算法用于 checkpoint streaming jobs,我认为这一设计是它取得成功的重要原因之一。Chandy-Lamport 算法能够在分布式系统中取得全局一致的快照,这允许我们用它来恢复一个 job 而不丢失或重复任何事件(所谓的 exactly-once)。
在故障恢复这个主题上,本质上,无论是 Flink 还是 RisingWave 都遵循和 AGC相同的原则:
在正常运行中,记录下可供恢复的 checkpoint,并保存在持久存储中 一旦系统发生不可继续的问题,从上一次 checkpoint 恢复并尝试继续
不过,RisingWave 的实现要轻量的多。
我们希望像 AGC 那样在一秒内完成重启,但是流计算的状态太大了!取决于workload,它可能从几 GB 到几 TB 不等,即便用今天的硬件,也需要一段时间来恢复全部的状态。
自从设计之初,我们就将计算节点看作几乎无状态的节点,具体地说,我们将计算节点内存以及磁盘中的数据看作缓存,有它能达到更好的性能,没有也不会影响 job 的运行。或者换一个说法,它在任务运行时 lazily 加载 checkpoint。该设计让 RisingWave 能够在毫秒级调度起新的任务而不用完整加载状态。
另一个容易被忽视的问题是 checkpoint 的新鲜度,或者说 checkpoint 频率。如果 AGC 重启后恢复到了更早的阶段,那么需要执行更多的步骤才能到达重启前的状态。流计算也是如此。例如,如果最后检查点在半小时前,那么,近半小时的输入就需要被重放,才能真正到达当前状态。
我们的解决方案是每隔 1s 进行一次 checkpoint,这听起来很频繁,不是吗?事实上,对于 S3 这样的对象存储,1 秒一次的put_object
是小菜一碟。RisingWave 的存储是从头设计的,基于 LSM-Tree 结构,每次 checkpoint 仅会上传那些新的改动,这使得它能高效地进行高频率的 checkpoint。在基准测试中,在 1s、10s 或是 1min 的 checkpoint 间隔下,性能表现都差不多。
在一些糟糕的案例中,我们观察到集群以每秒约十次的速率尝试恢复。当然,这对用户并不是什么好消息,我只是试图展示恢复机制本身的快速和可靠。作为参考,基于 Paxos 的数据库往往需要更久(例如 10s 或更多)才能从故障中恢复。
Control plane,一般指 Kubernetes,也在这期间发挥了重要的作用。Control plane 有一套独立的 liveness 检测机制,当机器出现问题时,它会将负载即时reschedule;或者当计算节点意外退出时,control plane 会立即将它拉起。大多数情况下,control plane 会保证集群中有指定数量的节点存活。
特别地,当失去节点时,相比于立刻将任务 scale in 到更少的节点上,RisingWave 倾向于等待新的节点加入。考虑到失败很可能与高负载相关,显然,此时 scale in 听上去不是个好主意。
4那么,元数据呢?
元数据是一个例外,它是一个真正的数据库,只不过是仅仅用于保存元数据,但是我们对它的要求与一个数据库并没有不同——高可用、高性能、最好还有ACID 事务。
RisingWave 的元数据保存在 etcd(我们正在重构这一部分,未来可能发生变化),一个多副本的 KV 数据库中。Meta Service 几乎是无状态的,这让它可以很容易地重启或迁移。
正如我们在大学学到的,可能出现需要网络分区的情况。Control Plane 并不总能保证 Meta Service 的唯一性,我们也永远不应该这样假设。因此,Meta Service 借助 etcd 实现了租约和 leader election 机制,确保任一时刻最多只有一个 Meta Service 为 primary,为集群提供元数据以及调度服务。
5容忍外部系统故障
与内部异常相对的是外部系统的故障。作为一个流计算系统,上下游往往存在两个或更多的系统,例如,从 Apache Kafka 中消费变更,最终输出到 PostgreSQL 中。
Source 端的异常处理简单直接:将它看作“静止”,没有新的数据流入。
Sink 端则更麻烦,RisingWave 的 SQL 接口很灵活,可以容易地创建出复杂的DAG 任务,例如一个任务可能既输出到 Data lake,又将过滤和分组后的数据输出到 Kafka。一旦其中一个下游系统突然变得缓慢或不可用,那么所有连接的任务都将受到影响。RisingWave 计划为 Sink 增加缓冲队列来解决这一问题,1.3 版本中已经支持了 Kafka append-only Sink。
6总结
就这样,RisingWave 的故障容忍以及高可用构建在 checkpoint-recovery 机制之上,凭借高效的 checkpoint 和 recovery,流计算任务不断重试,从暂时性的故障中恢复。
数据的持久性被外包给了 S3——一个多副本、高可用的对象存储(或任何与它相似的服务),元数据则是 etcd(一个多副本、高可用的数据库)。
最后,如果你对 Apollo 11 和 AGC 感兴趣,推荐你观看这个视频[2],本文的中关于 AGC 的图主要来自于这里。
参考资料
righto.com: https://www.righto.com/2019/07/bitcoin-mining-on-apollo-guidance.html
[2]这个视频: https://youtu.be/B1J2RMorJXM
✨GitHub: risingwave.com/github
💻 官网: risingwave.com
👨💻 Slack: risingwave.com/slack
📖 文档: risingwave.dev
🎬 B站: RisingWave中文开源社区
🔍 知乎: RisingWave 中文开源社区