一、关于 OpenDAL
OpenDAL : Open Data Access Layer,愿景是 One Layer, All Storage
- github : https://github.com/apache/OpenDAL
- 官网:https://opendal.apache.org/
- Python 开发文档:https://opendal.apache.org/docs/python/opendal.html | PYPI
功能架构
二、快速入门
Apache OpenDAL™可以通过其Rust核心和多语言绑定轻松集成到不同的软件中。
1、Rust core
OpenDAL的核心是用Rust编程语言实现的。在Rust程序中使用OpenDAL的最方便方法是 将OpenDAL Cargo crate 添加为依赖项。
安装
在您的项目目录中运行以下Cargo命令:
cargo add opendal
或者将以下行添加到您的Cargo. toml:
opendal = "0.46.0"
演示
试试看:
use opendal::Result;
use opendal::layers::LoggingLayer;
use opendal::services;
use opendal::Operator;
#[tokio::main]
async fn main() -> Result<()> {
// Pick a builder and configure it.
let mut builder = services::S3::default();
builder.bucket("test");
// Init an operator
let op = Operator::new(builder)?
// Init with logging layer enabled.
.layer(LoggingLayer::default())
.finish();
// Write data
op.write("hello.txt", "Hello, World!").await?;
// Read data
let bs = op.read("hello.txt").await?;
// Fetch metadata
let meta = op.stat("hello.txt").await?;
let mode = meta.mode();
let length = meta.content_length();
// Delete
op.delete("hello.txt").await?;
Ok(())
}
2、Java
OpenDAL的Java binding 作为org.apache.opendal:opendal-java:${version}
发布到Maven Central。
安装
Maven
一般来说,你可以先添加os-maven-plugin
根据你的平台自动检测分类器:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
</build>
然后将依赖项添加到opendal-java,如下所示:
<dependencies>
<dependency>
<groupId>org.apache.opendal</groupId>
<artifactId>opendal-java</artifactId>
<version>${opendal.version}</version>
</dependency>
<dependency>
<groupId>org.apache.opendal</groupId>
<artifactId>opendal-java</artifactId>
<version>${opendal.version}</version>
<classifier>${os.detected.classifier}</classifier>
</dependency>
</dependencies>
Gradle
对于Gradle,您可以首先添加com.google.osdetector
,用于根据您的平台自动检测分类器:
plugins {
id "com.google.osdetector" version "1.7.3"
}
然后将依赖项添加到opendal-java,如下所示:
dependencies {
implementation "org.apache.opendal:opendal-java:$opendal.version"
implementation "org.apache.opendal:opendal-java:$opendal.version:$osdetector.classifier"
}
Classified library
有关指定分类库的详细信息,请阅读专用说明。
演示
试试看:
// Configure service
final Map<String, String> conf = new HashMap<>();
conf.put("root", "/tmp");
// Construct operator
final Operator op = Operator.of("fs", conf);
// Write data
op.write("hello.txt", "Hello, World!").join();
// Read data
final byte[] bs = op.read("hello.txt").join();
// Delete
op.delete("hello.txt").join();
3、Python
OpenDAL的Python绑定作为opendal
发布到PyPI存储库。
安装
运行以下命令安装opendal
:
pip install opendal
演示
试试看:
import opendal
import asyncio
async def main():
op = opendal.AsyncOperator("fs", root="/tmp")
await op.write("test.txt", b"Hello World")
print(await op.read("test.txt"))
asyncio.run(main())
4、Node.js
OpenDAL的Node. js绑定作为opendal发布到NPM注册表opendal
。
安装
运行以下命令安装opendal
:
npm install opendal
演示
试试看:
import { Operator } from "opendal";
async function main() {
const op = new Operator("fs", { root: "/tmp" });
await op.write("test", "Hello, World!");
const bs = await op.read("test");
console.log(new TextDecoder().decode(bs));
const meta = await op.stat("test");
console.log(`contentLength: ${meta.contentLength}`);
}
三、愿景
1、Charter
One Layer, All Storage.
一层,全存储。
2、原则
原则是指导性原则。它们指导整个项目的决策。理想情况下,我们一直在做所有的事情。然而,在某些情况下,我们可能会被迫在稍微惩罚一个目标或另一个目标之间做出决定。在这种情况下,我们倾向于支持列表中较早出现的目标,而不是较晚出现的目标(但每种情况都不同)。
0) 开放社区
OpenDAL应该是一个开放的存储库。
OpenDAL是由OpenDAL PMC管理的ASF项目。在ASF,我们相信“社区优于代码”,并坚持阿帕奇的方式。我们的目标是开发OpenDAL来满足我们社区的需求。我们不维护私有版本或包含对他人无用的功能。
例如,OpenDAL更喜欢清晰易读的代码,因为这允许社区中的更多人加入开发。
1) 坚实的基础
OpenDAL应该是一个可靠的存储库。
OpenDAL是用户项目的坚实基础,用户可以信任OpenDAL对现实世界的存储服务执行操作。OpenDAL应该始终专注于构建坚实的基础。
例如,OpenDAL对AWS S3完整的多部分操作执行额外的错误检查,因为S3可能会返回错误以响应200状态代码,即使这可能会增加与“快速访问”冲突的额外成本。
2) 快速访问
OpenDAL应该是一个快速的存储库。
其快速访问确保OpenDAL以零开销实现存储支持。用户可以与OpenDAL集成,而无需担心额外成本。对于任何给定的存储服务,OpenDAL应该与SDK一样快,或者更快。
例如,OpenDAL使用Capability来描述不同服务的功能,并尽可能采用这些服务的本机功能。
3) 对象存储优先
OpenDAL应该是对象存储的第一个库。
OpenDAL确实支持所有存储服务,但它通常针对现代存储服务进行优化。在撰写本文时,我们可以说OpenDAL首先是对象存储。在设计功能时,OpenDAL倾向于优先考虑对象存储的优化。
例如,OpenDAL的Buffer设计主要针对基于HTTP的服务进行了优化,有助于减少额外分配、内存复制和内存使用。
4) 可扩展架构
OpenDAL应该是一个可扩展的存储库。
OpenDAL可以扩展到各种语言、后端和层。每个都是独立的,不依赖于其他的。用户可以组合不同的层,如指标、日志记录、跟踪和重试,并扩展自己的语言、后端和层。
例如,OpenDAL的核心从不依赖于单层的行为或依赖关系。用户可以在给定运算符上堆叠任意数量的层。
3、使用案例
谁是典型的OpenDAL用户?他们将如何使用OpenDAL?
3.1 基础设施建设者
例子:
用例:
- 构建数据库等存储系统
- 开发数据处理管道
- 创建备份和归档解决方案
主要关注点:
- 坚实的基础:需要保证存储操作的一致性和可预测性
- 快速访问:需要最小的开销和最佳的性能
- 为什么:基础设施服务要求可靠性和性能作为基本要求
3.2 应用程序开发人员
例子:
用例:
- 构建最终用户应用程序
- 开发CLI工具
- 创建Web服务
主要关注点:
- 快速访问:需要高效集成和最佳性能
- 对象存储优先:受益于现代云存储的优化
- 为什么:现代应用程序通常使用对象存储并需要响应式性能
3.3 平台开发者
例子:
用例:
- 构建AI/ML平台
- 开发云服务
- 创建开发者工具
主要关注点:
- 可扩展架构:需要定制和扩展存储能力
- 坚实的基础:需要可靠的存储操作
- 为什么:平台需要灵活性来适应各种用例,同时保持可靠性
四、Python API 调用
下面内容翻译自:https://opendal.apache.org/docs/python/opendal.html
import opendal
op = opendal.Operator("fs", root="/tmp")
op.write("test.txt", b"Hello World")
print(op.read("test.txt"))
print(op.stat("test.txt").content_length)
Or using the async API:
import asyncio
async def main():
op = opendal.AsyncOperator("fs", root="/tmp")
await op.write("test.txt", b"Hello World")
print(await op.read("test.txt"))
asyncio.run(main())
class Operator
Operator
is the entry for all public blocking APIs
Create a new blocking Operator
with the given scheme
and options(**kwargs
).
def layer(self, /, layer):
在现有操作器上添加新层def open(self, /, path, mode):
为给定路径打开一个类文件读取器def read(self, /, path):
将整个路径内容读取为字节def write(self, /, path, bs, **kwargs):
将字节写入给定路径def stat(self, /, path):
直接获取当前路径的元数据,无需缓存。def copy(self, /, source, target):
将源复制到目标def rename(self, /, source, target):
重命名文件def remove_all(self, /, path):
移除所有文件def create_dir(self, /, path):
在给定地址创建文件夹
注:要指示路径是目录,必须在路径中包含 尾随/。如果不这样做,可能会导致OpenDAL返回NotADirectory
错误。
Behavior- 在现有目录上创建将成功。
- Create dir 总是递归的,工作方式如
mkdir -p
def delete(self, /, path):
: 删除给定位置
注:删除位置不存在,不会返回错误。def list(self, /, path):
列出当前目录路径def scan(self, /, path):
以扁平方式列出目录def capability(self, /):
获取操作能力def to_async_operator(self, /):
转换为异步操作器
class AsyncOperator
AsyncOperator
是所有公开异步 APIs 的入口
使用给定的 scheme
和选项(**kwargs
)创建一个新的 AsyncOperator
。
def layer(self, /, layer):
在现有操作器上添加新层def open(self, /, path, mode):
为给定路径打开一个类文件读取器def read(self, /, path):
将整个路径内容读取为字节def write(self, /, path, bs, **kwargs):
将字节写入给定路径def stat(self, /, path):
直接获取当前路径的元数据(无缓存)def copy(self, /, source, target):
将源复制到目标def rename(self, /, source, target):
重命名文件def remove_all(self, /, path):
移除所有文件def create_dir(self, /, path):
在给定路径创建目录
注:要指示路径是目录,必须在路径中包含尾随/。如果不这样做,可能会导致OpenDAL返回“NotADirectory”错误。
表现:- 在现有目录上创建将成功。
- Create dir 始终是递归的,其工作原理类似于
mkdir-p
def delete(self, /, path):
删除给定地址
注:地址不存在,不会报错def list(self, /, path):
列出当前目录路径def scan(self, /, path):
以扁平方式列出目录def presign_stat(self, /, path, expire_second):
为 stat(head) 操作生成预签名,过期时间为expire_second
秒def presign_read(self, /, path, expire_second):
为读取操作生成预签名,过期时间为expire_second
秒def presign_write(self, /, path, expire_second):
为写入操作生成预签名,过期时间为expire_second
秒def capability(self, /):
def to_operator(self, /):
class File
一个类似文件的对象。可以用作上下文管理器。
def read(self, /, size=None):
读取最多size
字节,若未指定size
则读取至文件末尾def readline(self, /, size=None):
从文件中读取一行。换行符(\n
)留在字符串的末尾,如果文件不是以换行符结尾,则仅在文件的最后一行省略。如果指定了大小,则最多读取大小为字节的数据。def readinto(self, /, buffer):
将字节读取到预分配的可写缓冲区def write(self, /, bs):
将字节写入文件def seek(self, /, pos, whence=0):
将流位置更改为给定的字节偏移量,whence
默认为SEEK_SET
,可选值为SEEK_SET
或0
—— 流的开始(默认);偏移量应为零或正SEEK_CUR
或1
—— 当前流位置;偏移量可能为负SEEK_END
或2
—— 流的结束;偏移量通常为负值
返回新的绝对位置。
def tell(self, /):
返回当前流位置def close(self, /):
关闭流def flush(self, /):
刷新底层写入器,若文件以读取模式打开则无操作def readable(self, /):
返回流是否可读def writable(self, /):
返回流是否可写def seekable(self, /):
返回流是否可重新定位(仅限可读流)closed
: 返回流是否已关闭
class AsyncFile
一个类似文件的异步阅读器。可以用作异步上下文管理器。
def read(self, /, size=None):
读取最多size
字节,若未指定size
则读取至文件末尾def write(self, /, bs):
将字节写入文件
-
def seek(self, /, pos, whence=0):
将流位置更改为给定的字节偏移量。偏移是相对于whence
指示的位置来解释的。whence 的默认值是SEEK_SET
。whence
的值为: -
SEEK_SET
或0
—— 流的开始(默认);偏移量应为零或正 -
SEEK_CUR
或1
—— 当前流位置;偏移量可能为负 -
SEEK_END
或2
—— 流的结束;偏移量通常为负值
返回新的绝对位置。
def tell(self, /):
返回当前流位置def close(self, /):
关闭流def flush(self, /):
刷新底层写入器,若文件以读取模式打开则无操作def readable(self, /):
返回流是否可读def writable(self, /):
返回流是否可写def seekable(self, /):
返回流是否可重新定位(仅限可读流)closed
: 返回流是否已关闭
class Entry
path
: 入口路径。路径相对于运算符的根。
class EntryMode
def is_file(self, /):
返回是否为文件def is_dir(self, /):
返回是否为目录mode
: 表示此条目的模式content_md5
: 此条目的内容 MD5content_disposition
: 此条目的内容描述content_length
: 此条目的内容长度content_type
: 此条目的内容类型
class PresignedRequest
headers
: 返回 HTTP 请求头url
: 返回请求 URLmethod
: 返回 HTTP 请求方法
class Capability
Capability 用于描述 当前操作符 支持哪些操作。
read_with_override_content_type
: 操作器是否支持读取时覆盖内容类型write
: 操作器是否支持写入rename
: 操作器是否支持重命名write_total_max_size
: 服务支持的写入总最大大小(例如 Cloudflare D1 支持 1MB)read_with_override_cache_control
: 操作器是否支持读取时覆盖缓存控制stat_with_if_none_match
: 操作器是否支持带if-none-match
的 statwrite_with_cache_control
: 操作器是否支持写入时设置缓存控制delete
: 操作器是否支持删除presign_read
: 操作器是否支持预签名读取presign_write
: 操作器是否支持预签名写入shared
: 操作器是否支持共享read_with_if_none_match
: 操作器是否支持带if-none-match
的读取copy
: 操作器是否支持复制write_with_content_type
: 操作器是否支持写入时设置内容类型write_with_content_disposition
: 操作器是否支持写入时设置内容描述list_with_start_after
: 后端是否支持从指定位置开始列出list_with_limit
: 后端是否支持限制列出数量presign
: 操作器是否支持预签名write_multi_min_size
: 服务支持的多次写入最小大小(例如 AWS S3 要求至少 5MiB)write_can_append
: 操作器是否支持追加写入read_with_if_match
: 操作器是否支持带if-match
的读取read_with_override_content_disposition
: 操作器是否支持读取时覆盖内容描述write_can_multi
: 操作器是否支持多次写入create_dir
: 操作器是否支持创建目录list_with_recursive
: 后端是否支持递归列出blocking
: 操作器是否支持阻塞操作read
: 操作器是否支持读取stat_with_if_match
: 操作器是否支持带if-match
的 statwrite_can_empty
: 操作器是否支持写入空内容write_multi_max_size
: 服务支持的多次写入最大大小(例如 AWS S3 支持 5GiB)stat
: 操作器是否支持 statpresign_stat
: 操作器是否支持预签名 statlist
: 操作器是否支持列出
class WriteOptions`
2025-01-14(二)