数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载本文以 Apache OpenDAL 核心仓库中的 RFC 1477Remove Object Concept 为骨架结合core/core当前源码实现系统讲解 OpenDAL 为何移除Object中间层、Operator如何接管全部数据操作 API、Entry如何承担列表结果职责以及迁移旧代码时需要注意的破坏性变更。读完本文你将掌握 OpenDAL 自 v0.30 起“一切操作直连 Operator”的设计脉络并能将旧版op.object(path)风格代码平滑迁移到新 API。一、RFC 背景Object概念从“增强可用性”到“冗余中间层”1.1 Object Native API 的初衷OpenDAL 早期引入 RFC 0041Object Native API 是为了解决 API“不易用”的问题。以读取与查询元数据为例旧式SeekableReader写法冗长且需要调用方自己管理stream_len// 旧写法Object Native API 之前 let reader SeekableReader::new(op, path, stream_len); op.stat(path).run().await引入Object之后写法被大幅简化let reader op.object(path).reader().await?; op.object(path).stat().awaitObject的定位是“绑定路径的句柄”用户先通过op.object(path)取得一个Object再在其上执行reader、stat、scan等操作期望通过复用同一个Object减少重复构造。1.2 时代变化Object退化为Operator的包装器RFC-1477 指出了一个关键的架构事实当 list 操作被提升到Object级别之后Object实际上变成了Operator的包装器Operator唯一有意义的对外方法只剩Operator::object。来自 databend 的真实使用案例充分暴露了问题——用户几乎从不复用Object而是每次操作都重新构造if let Some(dir) dir_path { match op.object(dir).stat().await { Ok(_) { let mut ds op.object(dir).scan().await?; while let Some(de) ds.try_next().await? { if let Some(fi) stat_file(de).await? { files.push(fi) } } } Err(e) warn!(ignore listing {path}/, because: {:?}, e), }; }反复书写op.object(path)不仅冗长还带来两个实质性问题。额外开销Extra costObject::new()并非零成本构造它必须同时持有Operator内部句柄与路径字符串的Arcpub(crate) fn with(op: Operator, path: str, meta: OptionObjectMetadata) - Self { Self { acc: op.inner(), path: Arc::new(normalize_path(path)), meta: meta.map(Arc::new), } }每一次op.object(path)都伴随一次Arc分配与路径规范化。而在 RFC 1398Query Based Metadata 引入之后元数据查询已改为按需获取Object上缓存元数据的能力不再必要这一层包装的性价比进一步下降。概念复杂Complex conceptsObject一词在对象存储、面向对象编程、数据库等多个领域都有各自含义OpenDAL 很难给出一个无歧义的opendal::Object定义更糟糕的是用户很容易误以为opendal::Object是专为对象存储服务Object Storage设计的概念从而产生误导。1.3 提案结论RFC-1477 的结论非常干脆彻底移除Object这一中间 API 层让用户直接使用Operator同时将扫描/列表结果交给只承载路径与元数据的Entry以及对应的BlockingEntry。二、改造后的 Guide-level APIOperator一统天下RFC 为改造后的日常使用描绘了清晰的 API 蓝图这些 API 如今已在core/core/src/types/operator/operator.rs中全部落地实现// 读取 op.read(file).await?; // 读整个文件 op.range_read(file, 0..1024).await?; // 读部分内容RFC 提出的范围读 op.reader(file).await?; // 创建流式 Reader // 写入 op.write(file, bs).await?; // 一次性写入全部内容 op.writer(file).await?; // 创建流式 Writer // 元数据 op.stat(path).await?; // 获取路径元数据 // 删除 op.delete(path).await?; // 删除单个路径 op.remove(vec![path_a]).await?; // 批量删除多个路径 op.remove_all(path).await?; // 递归删除路径 // 目录 op.create_dir(dir/).await?; // 创建目录 // 列表 op.list(dir/).await?; // 列出目录一次性返回 op.scan(dir/).await?; // 递归扫描目录2.1 源码印证核心方法在Operator上的实际签名对照当前仓库实现core/core/src/types/operator/operator.rsRFC 中的 API 已完整落地并在此基础上发展出了xxx_with/xxx_options三阶方法族简化版 → 链式配置版 → 显式选项版RFC 提案 API当前实现位置返回类型read(path)operator.rs#L612-L615ResultBufferreader(path)operator.rs#L729-L731ResultReaderwrite(path, bs)operator.rs#L854-L857ResultMetadatawriter(path)operator.rs#L1022ResultWriterstat(path)operator.rs#L419-L421ResultMetadatacreate_dir(path)operator.rs#L569-L587Result()delete(path)operator.rs#L1810-L1812Result()remove_all(path)operator.rs#L2017-L2019Result()已标记 deprecatedlist(path)operator.rs#L2057-L2059ResultVecEntry几点实现细节值得注意read与reader的分工read一次性将整个文件读入内存Buffer适用于小文件源码注释明确建议大文件或需要精确内存管理时改用reader流式读取operator.rs#L589-L611。read_inner内部还会校验路径类型若对目录执行read会返回ErrorKind::IsADirectoryoperator.rs#L691-L698。create_dir的路径约定路径必须以/结尾否则返回ErrorKind::NotADirectoryoperator.rs#L569-L580这也与Entry::path“目录路径必须以/结尾” 的约定保持一致。write的返回值一次性write会返回写入后文件的Metadata方便调用方直接拿到 ETag、版本号等信息。remove_all已过时自 v0.55.0 起remove_all被标记为#[deprecated]官方建议改用op.delete_with(path).recursive(true).awaitoperator.rs#L1977-L2019。批量删除能力的演进RFC 中提出的op.remove(vec![...])在后续演进为delete_iter/delete_try_iter/delete_stream/delete_try_stream等迭代器/流式批量删除方法以及deleter()返回的Deleteroperator.rs#L1894-L1975后者可利用底层服务的批量删除能力。range_read的演进RFC 中提出的range_read在后续被吸收进ReadOptions体系如今通过op.read_with(path).range(0..10).await?或read_options显式传入范围实现operator.rs#L637-L640。2.2 流式列表list与lister的分工RFC 中list/scan返回Entry的设计在源码中进一步分化为两种形态list(path)一次性物化全部条目并返回VecEntry适合小规模目录源码注释明确提示大目录应改用listeroperator.rs#L2057-L2059。其语义为前缀式列举不要求父目录存在若path本身存在也会作为条目返回。lister(path)创建流式Lister逐条try_next()拉取支持recursive、limit、start_after、versions、deleted等选项operator.rs#L2211-L2213。list内部实际上也是通过Lister::create后try_collect实现的operator.rs#L2165-L2175。三、Blocking APIblocking::Operator的轻量接入RFC 明确要求提供blocking::Operator让阻塞场景的用户同样获得简洁体验// 这是一个廉价调用不涉及额外分配 let bop op.blocking(); bop.read(file)?; // 读全部内容 bop.write(file, bs)?; // 写全部内容当前仓库中的阻塞 API 位于 core/core/src/blocking/operator.rs并实现了stat、create_dir、read、reader、write、writer、delete、remove_all、list、lister等与异步版本一一对应的同步方法如 blocking/operator.rs#L314、blocking/operator.rs#L425、blocking/operator.rs#L488。一个值得注意的实现细节是blocking::Operator::new(op)需要从当前线程获取 tokio 运行时句柄Handle::try_current()若当前不在异步运行时上下文中会直接报错blocking/operator.rs#L132-L140。典型用法是在main函数中先进入全局 tokio runtime再构造阻塞 Operatorblocking/operator.rs#L106-L124。四、Entry列表结果的新载体RFC 规定扫描/列表结果将是Entry或BlockingEntry只用于列表条目包含与旧Object相同的字段impl Entry { pub fn mode(self) - EntryType; pub fn path(self) - str; pub async fn stat(self) - ResultMetadata; pub async fn metadata(self, key: impl IntoMetaKey) - ResultMetadata; ... }当前实现中Entry是一个极简的值类型只包裹一份Metadatacore/core/src/types/entry.rs#L21-L28#[derive(Clone, Debug)] pub struct Entry { metadata: Metadata, }它对外提供的方法包括path()返回相对 Operator 根的路径目录路径以/结尾、文件路径不以/结尾entry.rs#L36-L46name()返回路径的最后一个段basename同样遵循目录/文件的结尾约定entry.rs#L48-L54metadata()直接借用已随列表结果返回的Metadataentry.rs#L56-L59into_parts()消费自身拆分为(String, Metadata)元组entry.rs#L61-L64。对比 RFC 草案最终实现的mode()能力被整合进metadata().mode()通过EntryMode::FILE / DIR / Unknown区分条目类型——这也正是ObjectMode更名后的结果。由于Lister返回的Entry已携带元数据实际使用时通常无需对每条再发起一次额外stat请求直接读取entry.metadata()即可let mut ds op.lister(path/to/dir/).await?; while let Some(de) ds.try_next().await? { match de.metadata().mode() { EntryMode::FILE println!(Handling file: {}, de.path()), EntryMode::DIR println!(Handling dir: {}, de.path()), EntryMode::Unknown continue, } }五、Reference-level 变更命名体系的整体清理RFC 的参考级解释给出了核心迁移范式// 旧写法 op.object(path).read().await // 新写法 op.read(path).await同时要求一并重命名ObjectXxx系列结构体例如ObjectReader→Reader。这一命名清理在 v0.30 升级文档core/core/src/docs/upgrade.md#L1208-L1223中被完整记录具体包括正则替换object\((.*)\).reader\(\)为reader($1)建议逐个函数手工确认替换ObjectMetakey→MetakeyObjectMode→EntryModeErrorKind::ObjectXxx→ErrorKind::XxxAccessorMetadata→AccessorInfoObjectMetadata→Metadataoperator.metadata()→operator.info()。六、Drawbacks 与迁移注意一次覆盖面极广的破坏性变更RFC 直言这是一次主要的破坏性变更major breaking change几乎所有现有用法都需要重写。对迁移者而言重点注意事项如下API 全部上移凡是通过op.object(path)触发的方法read、write、stat、delete、scan、reader、writer等一律改为op.方法名(path)直连调用。返回值类型变化read返回Buffer而非旧式 reader列表返回VecEntry或流式Lister条目类型判断改用entry.metadata().mode()与EntryMode。递归删除的后续演进RFC 时代的remove_all在 v0.55.0 已废弃新代码应直接使用delete_with(path).recursive(true)。阻塞 API 的运行时前提blocking::Operator::new(op)必须在已进入 tokio 运行时上下文的线程中调用。七、设计价值与影响从架构视角看RFC-1477 的落地完成了 OpenDAL 一次重要的 API 收敛Operator成为唯一的用户入口路径作为每次调用的第一参数直接传入Entry退化为纯粹的“列表结果值”。这不仅消除了Object::new()的Arc分配开销与概念歧义更让 API 表面surface更贴近“存储访问”本身的直觉——读就是op.read(path)写就是op.write(path, bs)。从当前源码可以看出这一决策为后续xxx_with链式选项、Deleter批量删除、Lister流式列举、Reader/Writer高级能力如分片上传、版本读取的持续演进提供了干净的地基也印证了 RFC 文档中“让用户直接利用 Operator”这一核心判断的长远价值。参考链接RFC 1477Remove Object Concept本文主题文档Operator 核心实现全部数据操作 APIEntry 类型实现Blocking Operator 实现v0.30 升级指南Object 迁移清单RFC 0041Object Native APIObject 概念的引入源RFC 1398Query Based Metadata促使 Object 元数据缓存失去意义赞分享数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载相关推荐Apache OpenDAL RFC-3017 解析为何移除 oio::Write::copy_from 及其设计演进Apache OpenDAL RFC 3017 解析为何移除 oio::Write::copy_from 及其设计演进 导读 本文以 Apache OpenD数据存储后端Apache OpenDAL RFC-6189移除原生阻塞支持统一走 block_on 的阻塞 API 设计Apache OpenDAL RFC 6189移除原生阻塞支持统一走 block_on 的阻塞 API 设计 Apache OpenDAL 长期同时维护原生数据存储后端Apache OpenDAL 目录语义统一create_dir RFC 的设计演进与跨服务实现剖析Apache OpenDAL 目录语义统一create_dir RFC 的设计演进与跨服务实现剖析 Apache OpenDALOne Layer, All数据存储后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?