数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载本文围绕 OpenDAL 的 RFC-2083writer_sink_api展开系统讲解为什么对象存储抽象层需要流式写入能力、sinkAPI 的原始设计copy_from/pipe_from如何把用户数据直接“管道”进底层服务以及该设计在仓库中真实落地后经历的重构从oio::Write::sink被移除RFC-3017到最终演化为今天Writer::into_sink()、into_bytes_sink()、into_futures_async_write()这套零拷贝 Sink/AsyncWrite 适配器。读完本文你将理解 OpenDAL 写入路径的完整历史脉络并知道当前版本下应使用哪些 API 实现流式上传。背景为什么需要流式写入RFC 文档2083_writer_sink_api.md开宗明义当时的 OpenDAL 不支持流式上传用户必须先把数据加载进内存再分块调用writelet bs balabala(); w.write(bs).await?; let bs daladala(); w.write(bs).await?; ... w.close().await?;这种模式有两个主要缺点RFC Summary 与 Motivation 章节内存占用高数据必须先完整驻留内存。RFC 明确引用了 GitHub issue #1821 中用户报告的内存问题性能低因为用户数据必须先被缓冲到内存中才能发送到网络。因此 RFC 提议让 OpenDAL 提供一个 API允许用户把一个 stream 或 reader 直接传入 writer跳过“全量驻留内存”这一步。RFC 原始 API 设计copy_from与pipe_fromRFC 在 Guide-level explanation 中提出的Writer公共 API 有两个入口impl Writer { pub async fn copy_fromR(mut self, size: u64, r: R) - Result() where R: futures::AsyncRead Send Sync static; pub async fn pipe_fromS(mut self, size: u64, s: S) - Result() where S: futures::TryStream Send Sync static Bytes: FromS::Ok; }二者分别面向两类数据源copy_from接受任意futures::AsyncRead源例如异步文件、HTTP 响应体size参数声明数据源的总长度pipe_from接受任意futures::TryStream且要求流中的项S::Ok能转换为Bytes适合已经是分块流形态的数据例如上游对象存储的 Reader 分块流。RFC 给出的使用示例展示了流式写入 5 TiB 文件的场景// Start writing the 5 TiB file. let w op.writer_with( OpWrite::default() .with_content_length(5 * 1024 * 1024 * 1024 * 1024)); let r balabala(); // Send to network directly without in-memory buffer. w.copy_from(size, r).await?; // repeat... ... // Close the write once we are ready! w.close().await?;注意OpWrite::with_content_length的用法提前声明文件的总长度使得底层服务例如 S3 分片上传可以在不缓冲整份数据的情况下规划上传策略。RFC 同时承诺“The underlying services will handle this stream in the most efficient way possible”——即把“如何最高效地消费这个流”的决定权交给各 service 实现。底层设计oio::Write::sinkReference-level explanation 给出了该 RFC 的核心机制在原始层oio::Writetrait 上新增一个sink方法#[async_trait] pub trait Write: Unpin Send Sync { async fn sink(mut self, size: u64, s: Boxdyn futures::TryStreamOkBytes Send Sync) - Result(); }见 2083_writer_sink_api.md 的原始签名设计要点统一归一为字节流。OpenDAL 把用户传入的 reader 或 stream 都转换成Boxdyn TryStreamOkBytes Send Sync形式的字节流再交给 service。这样 service 侧只需面对一种输入形态降低了 trait 的复杂度原生流式 vs 回退缓冲。RFC 说明支持流式上传的服务可以直接消费这个流不支持的服务则可以用反复调用write的方式把整个流逐块写入即 sink 对不支持的 service 有天然的回退路径。content_length与sinksize 的区别Rationale 章节回答了一个关键问题OpWrite::content_length与sinkAPI 的size参数有什么差异OpWrite::content_length声明的是待写入文件的总长度sink的size参数表示本次传入的 reader/stream 的长度。当content_length恰好等于某次 sink 传入的size时某些服务可以优化为“单次请求写完全部内容”例如一个 PUT 请求而不是分片上传。这两个参数的语义区分是 service 层做上传策略优化的依据。落地情况sink 曾真实进入 OpenDAL 的 Raw APIRFC 并非停留在纸面。从 core/CHANGELOG.md 可以确认其完整的落地轨迹feat(core): Add Write::sink APIPR #2440oio::Write::sink进入核心随后多个服务逐个实现原生 sinkfeat: Implement sink for services s3PR #2508以及 azblob、gcs、oss、obs、cos、webdav 等服务的 sink 实现PR #2574/#2576/#2577/#2578/#2587/#2622feat: Add retry for Writer::sink operationPR #2896为 sink 操作增加了重试支持这正是 RFC “Future possibilities” 章节预告的方向refactor: Relax bounds on Writer::{sink, copy}PR #3027放宽了公共 API 的 trait bound。core/core/src/docs/upgrade.md 中 “Upgrade to v0.38” 一节也印证了这一点OpenDAL 在 v0.38 为 Raw API 添加了Write::sink这对依赖 raw API 的实现者是破坏性变更官方给出的快速适配方案是让自定义Write实现对sink()返回Unsupported错误——这恰好与 RFC 中“不支持流式的服务返回错误、由框架回退”的设计相衔接。后续演进oio::Write::sinkcopy_from被移除sink API 在实现中暴露了问题。后续 RFC-30173017_remove_write_copy_from.md2023-09-06记录了这一转折sinkAPI 落地后曾更名为oio::Write::copy_from签名变为pub trait Write: Unpin Send Sync { async fn copy_from(mut self, size: u64, src: oio::Reader) - Resultu64; }RFC-3017 指出了它的两大局限与既有的缓冲buffering和重试retry机制不兼容sink 的流式语义难以被 buffering/retry layer 统一拦截处理对 reader 强加所有权要求src: oio::Reader是值传递每次写操作都需要重新创建 reader使用上被复杂化。理想形态copy_from(mut self, size: u64, src: mut impl oio::Read)在当时的 Rust 与 hyper 约束下无法做到 object-safe 且被 hyper 的impl Stream static要求所限制。因此 RFC-3017 决定移除oio::Write::copy_from保留Writer::sink()/Writer::copy()等公共入口但内部改用futures::io::copy(mut r, w)基于AsyncWrite实现流式传输——即在 service 内部通过通用的异步写抽象完成 reader 到 writer 的管道而不是让 service 直接消费原始流。当前仓库状态sink 思想在公共 API 上的最终形态从当前仓库源码看oio::Writetraitapi.rs已经没有sink方法核心方法只剩write、copy_from注意此处的copy_from语义已变为“把已存在对象的某个范围原生拷贝进 writer”受Capability::write_can_copy_from能力位控制见 api.rs 的注释、close和abort。RFC-2083 想解决的“流式写入”需求如今由Writer上的三类 Sink/AsyncWrite 适配器承载定义于 writer.rsAPI返回类型定位源码位置into_sink()BufferSink实现futures::SinkBuffer零拷贝复用底层 Bytes无额外复制writer.rs#L303into_bytes_sink()FuturesBytesSink实现futures::SinkBytes零拷贝writer.rs#L418into_futures_async_write()FuturesAsyncWriter实现futures::AsyncWrite需要 ownedBuffer存在一次额外拷贝writer.rs#L361典型用法源自 writer.rs 的文档示例use bytes::Bytes; use futures::SinkExt; use opendal_core::Operator; use opendal_core::Result; async fn test(op: Operator) - Result() { let mut s op.writer(hello.txt).await?.into_sink(); let bs Hello, World!.as_bytes(); s.send(Buffer::from(bs)).await?; s.close().await?; Ok(()) }use futures::io::AsyncWriteExt; use opendal_core::Operator; use opendal_core::Result; async fn test(op: Operator) - Result() { let mut w op.writer(hello.txt).await?.into_futures_async_write(); let bs Hello, World!.as_bytes(); w.write_all(bs).await?; w.close().await?; Ok(()) }此外Writer还提供write_from(buf: impl Buf)writer.rs#L162用于从bytes::Buf高效写入对单块连续Bytes走零拷贝快路径对Chain等多块缓冲区则逐块提取其中Bytes块的提取只是引用计数递增、不发生数据复制。这套组合——零拷贝 Sink 适配器 可链式配置的 writer 构建器如.concurrent(8).chunk(256)见 writer.rs 文档示例——在功能上承接了 RFC-2083 “把流直接送进 writer、由底层以最高效方式处理”的初衷同时绕开了当初 sink trait 方法与 buffering/retry 层冲突的问题。对应的实现与单测分别位于 buffer_sink.rs、futures_bytes_sink.rs、futures_async_writer.rs而Writer自身的行为验证见 writer.rs 的 tests 模块。RFC 中预留的开放问题回到 RFC 原文的 “Future possibilities” 章节其中两项也都有了后续sink 的重试RFC 指出“sink API 本身无法重试但可以在流被拉取next遇到可重试错误时借助backon一类 crate 重新调用next来重试”。CHANGELOG 中的feat: Add retry for Writer::sink operationPR #2896表明这一方向在 sink 存续期间曾落地sink 移除后重试职责回归到统一的 retry layer 与AsyncWrite路径上。blocking 支持RFC 明确“We will add async support first”——先实现异步版本阻塞版本后置。这一点从当前仓库结构看仍然成立oio写入体系与Writer适配器全部基于异步。小结RFC-2083 是 OpenDAL 写入路径设计史中承上启下的一份提案它确立了“用户侧流式源 service 侧最高效消费 不支持时回退缓冲”的流式写入模型并真实经历了从oio::Write::sink落地S3、azblob、GCS、OSS、OBS、COS、WebDAV 等服务原生支持、增加重试到因 buffering/retry 兼容性与 reader 所有权问题被 RFC-3017 移除、重构为基于AsyncWrite的实现。对今天的用户而言需要流式写入时不再寻找sink/pipe_from这类 API而是直接使用Writer::into_sink()/into_bytes_sink()/into_futures_async_write()这组零拷贝或低开销适配器而在需要“把已有对象范围合并写入”的场景下Writer::copy_from(path, range)受Capability::write_can_copy_from能力位约束、单次最多 5 GiB提供了服务端的原生拷贝路径。理解这一演进脉络有助于在升级 OpenDAL 版本、阅读旧代码或自行实现 service 时准确对应历史文档与当前代码之间的 API 差异。赞分享数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载相关推荐Apache OpenDAL RFC-0554 深度解读以 BytesReader 重构 write 操作的设计与落地Apache OpenDAL RFC 0554 深度解读以 BytesReader 重构 write 操作的设计与落地 本篇技术指南围绕 Apache Ope数据存储后端Apache OpenDAL RFC-3017 解析为何移除 oio::Write::copy_from 及其设计演进Apache OpenDAL RFC 3017 解析为何移除 oio::Write::copy_from 及其设计演进 导读 本文以 Apache OpenD数据存储后端Apache OpenDAL 服务配置统一化从 Builder 到 Config 结构体的演进设计RFC 3197 深度解读Apache OpenDAL 服务配置统一化从 Builder 到 Config 结构体的演进设计RFC 3197 深度解读 导读 本文以 Apache数据存储后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考