暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

打造一个源连接器:RisingWave Code Camp 初体验

作者:Tushar Malik(开发者兼面条爱好者)



简单来说,RisingWave 是一个分布式云原生流式数据库。它是从零开始构建的,可从流消息队列中获取并处理数据,并能横向扩展。我的一个朋友在参加 Kubecon EU 大会时遇到了 RisingWave 背后的团队,我就是通过他得知了 RisingWave Code Camp(下文简称“Code Camp”)。这是个专为提高初级开发人员的软件和数据库系统技能而开设的导师制项目。由于我并不熟悉 Rust 并且对数据库开发还有些心虚,所以一开始我还挺犹豫要不要申请加入 Code Camp。不过转念一想,我确实需要找个理由学习 Rust,并且一想到能够与构建 RisingWave 的大牛们合作,我就来了精神!

Code Camp 共有五个项目可以申请,不过考虑到我对 Rust 的熟悉度以及其他项目所需的专业知识,我最终申请了连接器项目。随后,我完成了一项资格认定任务——创建Apache Kafka 的 Pub/Sub 演示,并完成基准测试。任务完成后,我就被导师选中并成功加入 Code Camp 啦!


源连接器

源连接器是 RisingWave 从流数据源获取数据的组件。截至本文撰稿时,RisingWave 已经内建了 Apache Kafka、Apache Pulsar 和 Amazon Kinesis 的连接器。PR#5476 合并之后,RisingWave 将会自带 Google Cloud Pub/Sub 连接器。在本文中,我将回顾我在 Code Camp 实现连接器的过程。

Code Camp 启动

在 Code Camp 启动阶段,我首先在 Slack 和 Zoom 上与我的 Mentor 进行了沟通。期间,我们共同设定了训练预期、初步的时间节点,并决定每周开一次会,平时则通过 Slack 进行常规的异步沟通。
  • 学习 Rust

RisingWave 是用 Rust 语言编写的。作为一种现代的低级语言,Rust 拥有独特的数据所有权模型,因此非常注重安全性。由于其独特的设计,Rust 与我之前用过的其他语言非常不同。因此,作为我的第一项任务,我的 Mentor指导我通过 Rustlings 练习来学习 Rust。另外,我还通过 Rust BookRust by Example 找到了很多有帮助的内容。这样一来,我很快就开始熟悉这门语言,并在两周内到达了初步掌握的程度。当然,这个时候我对自己的语言水平还不是很自信,也不是特别理解其中的复杂之处。但我的想法是我能够在工作中学习。我在之前参与过的项目中经常边做边学,这次我也有十足的信心。


  • RisingWave 架构

虽然对于构建连接器来说不是必要的,但理解 RisingWave 的架构对我来说是至关重要的。幸运的是,RisingWave 有完善的文档,其中就包括有关架构设计的文档。我还跟 Bohan Zhang 和 Yanghao Wang 两位 Mentor 一起开展了架构评审环节。期间 Yanghao 向我介绍了与连接器有关的数据库部件。说实话,我对这些知识有些招架不住,不过好在我研究过架构文档,这次介绍演示也让我对理解整个系统和连接器在系统中的作用更加自信了。

总结来说的话,源在 RisingWave 中被划分成不同的分片。分片是数据源中代表一个逻辑分片/分区的单位,分片随后被转移至分片读取器。读取器负责从源分片中读取消息,然后将它们转化成标准的、与源无关的数据类型,即源消息。

  • 选择一个 Pub/Sub Crate

Rust 拥有非常丰富的软件包生态系统,这些软件包被称作 crate。对于 Google Pub/Sub 连接器来说,我们可以从头开始编写一个 crate,或者找一个符合我们目的的现成的 crate。为此,我研究了 Rust 的 crate 登记网站 crates.io,分析了相关的功能支持状态、在相关库上的开发活动以及 issue 和讨论等使用指标。最终,我在 google-cloud-pubsub 发现了一个非常棒的 crate。它是用异步 Rust 编写的,支持连接器所需的大多数功能。选好了 crate 之后,我编写了一个 PoC 演示 来进行测试。

编程

  • 连接器的实现

RisingWave 大量使用 Rust 宏来改善和精简开发者的开发体验。对于源连接器来说,它使用宏来在编译时生成具体的连接器实现。在实际操作中,这意味着连接器的构建会涉及到某些特征的实现(Rust 接口),然后将其插入到代码生成宏中以避免重复样板代码。

我实现的第一个特征是分片枚举器。该特征负责枚举分片(通常每个分片代表源中的逻辑分区)。我们遇到的第一个挑战是 Google Pub/Sub 的特殊设计。Google Pub/Sub 是一个高级系统,它将 GCP 可能使用的任何逻辑分片从客户端抽象出来。我曾有段时间不确定应该如何继续。不过,在与 Bohan 和 Yanghao 对具体实现细节等问题进行充分讨论之后,我们决定(用 Bohan 的话来说)对分片的抽象化进行自上而下的调整。通过将分片的数量作为 Pub/Sub 源的一项配置参数,我们就可以从单一 Pub/Sub topic 中读取数据时进行分布式处理。

通过这种方式,我们就可以建立 n 个分片来读取负责在活跃 consumer 之间分配消息负载的 Pub/Sub 流。这样做的目的是在多个分片之间分配流处理。用户应在创建源时考虑到工作负载。

在解决完分片枚举器之后,就到了实现分片读取器特征的时候了。读取器是连接器的一部分,负责在给定存储的源分片的情况下从源实际拉入消息,然后将其提供给计算节点进行处理和存储。要实现这些,第一步就是从源原生格式中提取消息,然后将其转化为 RisingWave 原生源消息。从技术角度来说,实现这一特征相对简单,因为这规避了因性能问题对上游进行变更的需求。我将会在下一节中详细讨论这一问题。

随着分片枚举器和读取器的完成,连接器需要支持回溯到所消耗的消息的恢复点——这是 RisingWave 在出错时用于回滚 consumer 状态的操作。由于特殊的 Pub/Sub 设计,我们很快就遇到了另一个问题:在大多数流处理系统中,所有的消息不管确认状态如何都会留存一段时间。然而在 Pub/Sub 中,除明确设定外,已确认的消息会默认丢弃,而留存消息会带来额外的存储费用。经过讨论之后,我们决定实施“确认后留存”的策略,因为回滚是一个非常重要且高频的操作。我们还决定只接受用户预制的订阅,以确保连接器需求的透明性。对我来说,这就像是在保证我们对 Pub/Sub 的需求与给予用户一定控制权之间取得了最佳的平衡。

  • 上游工作

RisingWave 作为一个分布式、高容错、性能关键的系统,我们的最初选择的 Pub/Sub crate 并不能完全满足需求。这其中的第一个问题就是缺少批确认 API(batch-acknowledgment API)。Pub/Sub 要求消息经过 consumer 确认消息已被消耗并可以安全地摒弃。consumer 确认的缺失意味着 Pub/Sub 将会在消息提取过程中重新传递该消息。消息可以通过单个 API 调用单独或批量确认,但单独确认的网络开销对于 RisingWave 的高容量用例来说是不可接受的。这就是我创建的第一个上游 issue。据作者说已经有了支持这一功能的解决方案,且很快就会发布相关补丁。

后来,在我们讨论非常关键的回寻(seek-back)功能时,我又创建了一个上游 issue 和相关的 pull request 以支持 google-cloud-rust 中时间戳和基于快照的查找操作。这个 pull request 的工作很有挑战性,因为我处理了用于测试的 Google Pub/Sub 模拟器的限制等等,但好在这些困难都在 crate 维护者的及时帮助下得到了改善。这是我第一次提交基于 Rust 的代码,在我的 Code Camp 旅程中算是一次小小的但值得骄傲的成就。
Google Cloud SDK 在主机文件系统中通常将认证与 JSON 文件相绑定。对于像 RisingWave 这样的动态分布式系统,这种限制意味着 credential 会从系统边界泄露到节点文件系统中。这是不太理想的,而且在系统的其他地方也没有使用。这里 cue 一下我对上游系统最后一项贡献:支持独立于文件系统的身份验证。这里我给 google-cloud-rust 打了补丁,额外再支持一个的环境变量,使其能够从环境中读取认证消息。
从整体上看,对上游的贡献是我在 Code Camp 期间取得的最有个人价值的一个个小成就,我很高兴能做出这些贡献。
  • 测试

在完成重要工作之后,我们需要通过自动化测试来确保一切运转顺利且能持续运行。在最后一两次周例会中,Bohan 和我讨论了测试的问题。我们首先探讨了单元测试,我们认为在连接器中并没有多少能够测试的部分。随后是端到端测试,这对于确认整个连接器的行为符合预期是至关重要的一步。在导师的指导下,我首先针对 Apache Kafka 进行了现有的 Sqllogictest [1] 端到端测试,随后我又提出了如何在测试基础中使用 Pub/Sub 的问题。这是因为 Google Pub/Sub 是一个托管的云服务,它需要一个 GCP 账号,如果没有 Pub/Sub 模拟器就很难在测试中使用 Pub/Sub 功能, 因为它支持在测试环境中使用本地托管的模拟器。另外,Bohan 还担心模拟器依赖项的可及性问题,不过我最终找到了解决办法~
在解决了如何实现的问题之后,我开始着手将测试设置和模拟器整合到现有的由 RiseDev [2] 工具管理的 CI 基础设施中。我起初并没有预想到这一工具编写方式的复杂性及其结构的精妙性,不过,我独立对其进行了钻研,并非常自豪地将 Pub./Sub 模拟器整合到由该工具管理的开发环境中。除了与 RiseDev 整合之外,我还编写了相关的脚本将模拟器安装到一些配置目标上,以便在开发者环境中开启它。随后,我编写了一些 Rust 脚本在模拟器中建立测试数据,随后又编写了 Sqllogictest 脚本以验证所有部分能够顺利运行。到了这一步,所有部分都能在本地顺利运行了,不过,我的主要目标是让其在 CI 过程中运行,这需要像一个真正的开发者那样进行进一步的优化。

小结

Code Camp 是我最近参与过的最有获得感的体验。我非常幸运能够遇到一些行业大牛并与他们展开合作。在课程中,我从对 Rust 和流处理系统一无所知,到成为一名能够使用该语言的新手,再到为最牛的流式数据库贡献代码,收获良多。

与导师的会议也让我受益匪浅。我们在长达三个月的 Code Camp 获得了非常棒的体验,其中,Bohan 还顺带夸了我的代码编辑器。当他说我的编辑器看起来像是 Vim 时我还挺惊讶的,这其实就是 Visual Studio Code,只不过看起来稍微前卫了“亿”点点。在那次会议中,我们还探讨了编译速度过慢的问题,我起初以为是由于我编译机器不是M系列芯片的 Mac,后来我才发现我的 LLD 连接器设置也有问题。

我的开发环境

或许,最重要的一点是我的导师们始终能够耐心地帮我解决遇到的困难和问题。由于我只能在学校里参加大部分的网络会议,不免有些吵,而且网络连接也是个大问题。有一次,我们在讨论学习进度,由于网络连接一直出问题,我只能提前退出会议,招呼也没有打。但 Bohan 特别贴心地告诉我可以等网络转好的时候再与他异步沟通。

这期间的美好体验还有很多很多,这次经历也让我意识到我对数据库和流处理系统还有非常多的东西要学习。经过这次学习和体验,我可以说我现在是一个更加优秀的开发者了。

[1]: Sqllogictest 是一个 Rust crate,它提供的测试框架能验证一个 SQL 数据库的正确性。这个 crate 在 Rust 中实现了一个 Sqllogictest 解析器和运行器。可以点这里查看源码。

[2]: RiseDev 是 RisingWave 的开发者工具,旨在通过自动化和封装常见流程和设置来改善开发人员的体验。要了解更多,可以阅读这篇博客文章




关于 RisingWave

RisingWave是一个云原生SQL流式数据库。其旨在降低构建实时应用的门槛以及成本。

 ✨ GitHub: risingwave.com/github

 💻 官网: risingwave.com

 👨‍💻‍ Slack: risingwave.com/slack

📖 文档: risingwave.dev
💬 社区用户交流群risingwave_assistant

文章转载自RisingWave中文开源社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论