- 数据存储
- 后端
【免费下载链接】opendal
Apache OpenDAL: One Layer, All Storage.
本文以 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 的三个侧面:
- 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集成持续推进; - Working 层面:聚焦列表能力深化(路径
/限制、start-after推广)与集成生态落地; - 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.
相关推荐
Apache OpenDAL 目录列表能力演进:从 ObjectStream RFC 到现代 Lister API
Apache OpenDAL 目录列表能力演进:从 ObjectStream RFC 到现代 Lister API 导读 本文以 OpenDAL 核心仓库中的
数据存储后端Apache OpenDAL D 绑定实战指南:构建、API 与并行读写
Apache OpenDAL D 绑定实战指南:构建、API 与并行读写 Apache OpenDAL 的 D 语言绑定(位于 bindings/d https
数据存储后端OpenDAL Lister API:从 RFC 2774 到 list/lister 分层设计的实现剖析
OpenDAL Lister API:从 RFC 2774 到 list/lister 分层设计的实现剖析 本篇围绕 Apache OpenDAL 的 RFC《
数据存储后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考