ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Apache OpenDAL OwO 系列首发解读:v0.40 的 API 合并、Lister 重构与生态扩张

Apache OpenDAL OwO 系列首发解读:v0.40 的 API 合并、Lister 重构与生态扩张
  • 数据存储
  • 后端

【免费下载链接】opendal

Apache OpenDAL: One Layer, All Storage.

项目地址:https://gitcode.com/gh_mirrors/op/opendal
点击查看免费下载

本文以 Apache OpenDAL 官方发布博客《OwO #1: The v0.40 Release》为骨架,结合当前仓库中的核心源码(core/core/src)与升级文档(core/core/src/docs/upgrade.md),系统梳理 v0.40 版本在核心库 API 设计、语言绑定、应用与集成生态上的关键变化,并还原其背后的 RFC 演进脉络,帮助读者理解 OpenDAL 从"追加写与普通写分离"走向"统一 Write 接口 + 流式 Lister + 元数据预取"这一 API 哲学的转变过程。

OwO(Outcome、Working、Outlook)是 Apache OpenDAL™ 的版本发布博客系列,用于向社区同步当前的工作成果(Outcome)、正在进行的工作(Working)与未来的探索方向(Outlook)。本文对应系列的第一篇《OwO #1: The v0.40 Release》,发布于 2023 年 9 月 15 日,围绕即将发布的 v0.40 展开。读懂这篇发布说明,读者可以掌握:v0.40 中append与write的合并方式、list/lister双轨 API 的正确选型、list_with().metakey()的元数据预取用法,以及oay、oli、object_store等周边生态项目的定位。

OwO 是什么:OpenDAL 的版本发布博客系列

OwO 是 Outcome(成果)、Working(进行中)、Outlook(展望)三个词的缩写。作为 Apache OpenDAL 的发布博客系列,每一期都按这三个维度组织内容:

  • Outcome(第一个 O):总结当前版本取得的关键成果;
  • Working(中间的 w):同步正在推进中的工作;
  • Outlook(第二个 O):讨论 OpenDAL 未来的方向。

在 v0.40 时期,OpenDAL 的总体架构由四个主要组成部分构成:

  • Core:用 Rust 编写的核心库;
  • Bindings:由 OpenDAL Rust 核心驱动的各语言绑定;
  • Applications:基于 OpenDAL Rust 核心构建的应用;
  • Integrations:与其他项目的协作集成。

这四个部分也正是后续章节的组织线索,读者可以在当前仓库的目录结构中一一对应:核心实现在 core/core,语言绑定在 bindings,集成项目在 integrations。

Outcome:v0.40 的交付成果

Core 核心库:从"功能堆叠"到"API 统一"

合并 Append 与 Write(RFC-2578)

自 v0.36 起,OpenDAL 就支持了独立的append操作。但团队发现,append 与 write 之间存在大量重复逻辑,因此决定将两者合并为一个统一的写函数。此后用户无需再单独处理 append:

let mut w = op.writer_with("test.txt").append(true).await?; w.write(content_a).await?; w.write(content_b).await?; w.close().await?;

这样,用户可以在自己的业务逻辑中直接复用Writer,而无需区分"新建写入"与"追加写入"两套路径。这一合并最终固化为 RFC-2578(Merge Append Into Write),在 core/core/src/docs/upgrade.md 中明确给出了迁移指引:

  • 一次性写入文件,请使用op.write();
  • 追加写入文件,请使用op.write_with().append(true)替代原来的op.append();
  • 同样的规则适用于writer()与writer_with()。

从当前源码可以印证这一设计已完全落地:在 operator_futures.rs 中,FutureWriter::append(mut self, v: bool)直接将标志位写入写参数(self.args.append = v),而 ops.rs 中的OpWrite::append()则供底层服务在运行时查询该标志,决定走新建还是追加路径。也就是说,append从"独立的操作"降级为"Write 操作的一个布尔选项",这是 v0.40 最典型的 API 简化范例。

新的 Lister API(RFC-2774)

为了提升 API 一致性,v0.40 对列目录(listing)系列函数做了调整:新增了list与list_with两个一次性返回全部条目的方法,并把原先的list重命名为流式的lister与lister_with:

// 旧 API let lister: Lister = op.list("dir").await?; // 新 API let entries: Vec<Entry> = op.list("dir").await?; let lister: Lister = op.lister("dir").await?;

这一改动使 API 语义更加统一:一次性取回全部条目用list,流式逐条消费用lister。在 RFC-2774 的迁移说明中还有三条关键指引:

  • 一次性列目录,使用list();
  • 递归列出目录,使用list_with().delimiter("")或lister_with().delimiter("")(替代原来的scan());
  • 流式列出,使用lister()或lister_with()。

对照当前源码 operator.rs,可以看到这套双轨设计被完整保留:list()(L2057)与lister()(L2211)是便捷入口,list_with()(L2104)与lister_with()(L2246)则返回可链式配置选项的 Future。源码注释同时提示:list会把整个列表物化进内存,面对大规模结果集应优先使用lister流式处理。

List With Metakey(RFC-2779)

为了加速 list 操作,v0.40 支持在列目录过程中顺带获取并缓存元数据,从而省去额外的独立 metadata 调用:

let entries: Vec<Entry> = op .list_with("dir/") .metakey(Metakey::ContentLength | Metakey::ContentType).await?; // 直接使用元数据! let meta: &Metadata = entries[0].metadata();

这一设计让元数据获取变得更直观:用户不再需要为每个 entry 单独发一次metadata()请求,而是声明自己关心的元数据字段集合(Metakey位标志),OpenDAL 在列表过程中尽力补齐。

其底层原理记录在 RFC-2779 设计文档 中:对每个服务而言,stat会返回完整的元数据集(如s3返回ContentLength | ContentType | LastModified | ...,fs只返回ContentLength | LastModified),而list通常只返回部分元数据(s3返回ContentLength、LastModified,fs则一个都不返回)。因此list得到的 entry 元数据可能处于三种状态:已填充(Filled)、不存在(NotExist)或未知(Unknown)。metakey机制允许 OpenDAL 将用户声明的元数据需求下传给底层服务,服务"尽力而为"地通过列表响应或补充stat调用满足需求。RFC 还提出了stat_complete_metakey能力位,用于描述某服务通过stat能取到的完整元数据集合,Lister据此决定是否需要补发stat请求。升级文档中的"Before / After"对照(upgrade.md)直观展示了这一 API 如何消除循环内的逐条metadata()异步调用。

演进说明:metakey是 v0.40 时代的重要设计,但在后续版本中其概念被进一步简化。根据 RFC-5314(Remove Metakey) 与 upgrade.md,OpenDAL v0.51 移除了Metakey概念,改为由服务声明MetadataCapability并"尽最大努力返回尽可能多的元数据"。读者在阅读旧代码或迁移历史项目时可留意这一版本差异。

Buffered Writer

v0.40 为核心库增加了通用的缓冲写入支持,用户只需为 writer 指定缓冲大小:

let w = op.writer_with("path/to/file").buffer(8 * 1024 * 1024).await?

升级文档(upgrade.md)详细说明了其行为语义:

  • 不指定 buffer时,每次调用write()都会直接调用底层存储;
  • 指定 buffer 后,只有缓冲写满或调用close()时才会真正落到底层存储。

此外,buffered writer 还带来一个重要便利:用户不再需要为 writer 预先指定content_length()。迁移示例为:

- let mut w = op.writer_with("path/to/file").content_length(1024).await?; + let mut w = op.writer_with("path/to/file").await?;

从当前源码看,这一缓冲机制在 operator_futures.rs 中演化为chunk()选项("Sets chunk size for buffered writes"),即设定单次写入的分块大小,例如.chunk(8 * 1024 * 1024)会让数据在close时以单次 API 调用发送。

其他核心改进

v0.40 还移除了原有的range_read与range_reader接口,统一收敛为read_with().range()与reader_with().range()(upgrade.md):

- op.range_read(path, range_start..range_end).await?; + op.read_with(path).range(range_start..range_end).await?;
- let reader = op.range_reader(path, range_start..range_end).await?; + let reader = op.reader_with(path).range(range_start..range_end).await?;

这再次体现了 v0.40 的整体基调:把分散的专用 API 并入统一的、可选项驱动的*_with()接口族。更多细节可查阅 core/CHANGELOG.md。

Bindings 语言绑定:两个首发、两个新增

v0.40 在语言绑定层面迎来了重要里程碑:

  • C++:opendal-cpp 达到首个可发布状态,欢迎社区试用并提供反馈;
  • Haskell:opendal-hs 达到首个可发布状态;
  • Java:opendal-java 在此版本中启用了更多可用服务,用户可以访问 Rust 核心默认未开启的服务(如redis);同时启用了 blocking 层,允许用户以阻塞方式访问s3等服务;
  • 全新绑定:新增 opendal-dotnet 与 opendal-php 两个语言绑定项目。

这些绑定均由 OpenDAL Rust 核心驱动(各绑定目录下均有对应的Cargo.toml与 Rust FFI 层),印证了"Core + Bindings"这一架构下"一次实现、多语言复用"的扩展模式。

Applications 应用层:oay 与 oli

  • [oay](OpenDAL Gateway):允许用户通过既有协议(如s3、webdav)访问 OpenDAL 支持的服务,工作方式类似一个把请求转发到 OpenDAL 服务的代理。本版本实现了基础的webdav支持——用户可以把任意存储服务"变身"为 WebDAV 服务器。
  • [oli](OpenDAL CLI):允许用户像使用s3cmd、gcloud那样通过命令行访问存储服务。本版本修复了一些体验问题并完善了文档。

Integrations 集成:object_store

object_store 的目标是在 OpenDALOperator之上实现object_storecrate 的 trait,让用户可以把 OpenDAL 作为object_store的后端使用。该集成在 v0.40 时"大部分功能可用",但仍有一些 OpenDAL 尚未支持的边缘场景;此版本在集成层面没有新进展,团队正在等待相关 issue("Allow list paths that do not end with/")的解决。

Working:正在进行的工作

v0.40 发布时,团队同步推进着以下事项:

  • object_store支持:让该集成稳定可用,并为其找到实际用户;
  • 移除路径必须以/结尾的限制:实现"列出不以/结尾的路径";
  • 扩展start-after支持:将分页续列能力推广到更多服务(对应 issue #2786)。

其中"路径/限制"与start-after都属于列表(list)能力的深化,与上文 RFC-2774 的 Lister 重构一脉相承:先把 API 形态统一,再逐步补齐各服务的能力差异。

Outlook:面向未来的探索

OpenDAL 团队在 v0.40 时期已经开始探索一些创新方向:

  • OpenDAL REST/gRPC API:为 OpenDAL 提供 REST/gRPC 服务端;
  • OpenDAL Cache:OpenDAL 原生的缓存库,让用户更高效地访问数据;
  • OpenDAL File System:基于 OpenDAL 构建的只读文件系统(Rust 实现);
  • kio-opendal:由 OpenDAL 驱动的 KIO 插件,让用户在 KDE Dolphin 文件管理器中访问不同的存储服务;
  • gvfs-opendal:由 OpenDAL 驱动的 gvfs 插件,让用户在 GNOME Files 中访问不同的存储服务。

这些方向从"数据访问协议层"(REST/gRPC、文件系统)延伸到"桌面集成层"(KIO、GVFS),展现了 OpenDAL 作为统一存储访问层的横向野心。欢迎社区参与讨论。

总结

作为 OwO 系列的开篇,《OwO #1》完整呈现了 OpenDAL v0.40 的三个侧面:

  1. Outcome 层面:核心库完成了append并入write(RFC-2578)、Lister API 双轨化(RFC-2774)、list_with().metakey()元数据预取(RFC-2779)与 Buffered Writer 四大 API 演进;C++/Haskell 绑定首发、dotnet/php 绑定加入,oay 网关获得 WebDAV 能力,object_store集成持续推进;
  2. Working 层面:聚焦列表能力深化(路径/限制、start-after推广)与集成生态落地;
  3. Outlook 层面:探索 REST/gRPC 服务、缓存、文件系统与桌面文件管理器插件等新形态。

对开发者而言,v0.40 的核心启示是 OpenDAL 的 API 设计取向:能用选项表达的能力(append、range、buffer)就并入统一的*_with()接口,能用类型区分语义的场景(一次性list与流式lister)就显式分流。这些设计决策至今仍能在 core/core/src/types/operator/operator.rs 与 core/core/src/types/operator/operator_futures.rs 的源码中直接观察到,是理解 OpenDAL API 演化历史的绝佳样本。

  • 数据存储
  • 后端

【免费下载链接】opendal

Apache OpenDAL: One Layer, All Storage.

项目地址:https://gitcode.com/gh_mirrors/op/opendal
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进