作者:京东科技 贾世闻

对象存储是云的基础组件之一,各大云厂商都有相关产品。这里跟大家介绍一下rust与对象存储交到的基本套路和其中的一些技巧。

基本连接

我们以 aws 对象存储的sdk为例来说说基本的连接与操作,目前对象存储的客户端除了aws的还有一些第三方库,不过从功能和工程完整度上来讲 aws 的rust 库最为优秀,而且公有云市场上的大部分对象存储产品都与其兼容。作者验证过aws、京东云、阿里云。主要的增删改查功能没有什么差别。

  • 建立客户端

let shared_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::new(
"LTAI5t7NPuPKsXm6UeSa1",
"DGHuK03ESXQYqQ83buKMHs9NAwz",
None,
None,
"Static",
)))
.endpoint_url("http://oss-cn-beijing.aliyuncs.com")
.region(Region::new("oss-cn-beijing"))
.build();
let s3_config_builder = aws_sdk_s3::config::Builder::from(&shared_config);
let client = aws_sdk_s3::Client::from_conf(s3_config_builder.build());

建立Client所需要的参数主要有你需要访问的oss的AK、SK,endpoint url 以及服务所在的区域。以上信息都可以在服务商的帮助文档查询到。

  • 对象列表

let mut obj_list = client
.list_objects_v2()
.bucket(bucket)
.max_keys(max_keys)
.prefix(prefix_str)
.continuation_token(token_str); let list = obj_list.send().await.unwrap();
println!("{:?}",list.contents());
println!("{:?}",list.next_continuation_token());

使用list_objects_v2函数返回对象列表,相比list_objects函数,list_objects_v2可以通过continuation_token和max_keys控制返回列表的长度。list.contents()返回对象列表数组,list.next_continuation_token()返回继续查询的token。

  • 上传文件

let content = ByteStream::from("content in file".as_bytes());
let exp = aws_smithy_types::DateTime::from_secs(100);
let upload = client
.put_object()
.bucket("bucket")
.key("/test/key")
.expires(exp)
.body(content);
upload.send().await.unwrap();

指定bucket及对象路径,body接受ByteStream类型作为文件内容,最后设置过期时间expires,无过期时间时不指定该配置即可。

  • 下载文件

let key = "/tmp/test/key".to_string();
let resp = client
.get_object()
.bucket("bucket")
.key(&key)
.send()
.await.unwrap();
let data = resp.body.collect().await.unwrap();
let bytes = data.into_bytes(); let path = std::path::Path::new("/tmp/key")
if let Some(p) = path.parent() {
std::fs::create_dir_all(p).unwrap();
}
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path).unwrap();
let _ = file.write(&*bytes);
file.flush().unwrap();

通过get_object()函数获取GetObjectOutput。返回值的body 就是文件内容,将 body 转换为 bytes,最后打开文件写入即可。

  • 删除文件

let mut keys = vec![];
let key1 = ObjectIdentifier::builder()
.set_key(Some("/tmp/key1".to_string()))
.build();
let key2 = ObjectIdentifier::builder()
.set_key(Some("/tmp/key2".to_string()))
.build()
keys.push(key1);
keys.push(key2)
client
.delete_objects()
.bucket(bucket)
.delete(Delete::builder().set_objects(Some(keys)).build())
.send()
.await
.unwrap();

delete_objects 批量删除对象。首先构建keys vector,定义要删除的对象,然后通过Delete::builder(),构建 Delete model。

大文件上传

let mut file = fs::File::open("/tmp/file_name").unwrap();
let chunk_size = 1024*1024;
let mut part_number = 0;
let mut upload_parts: Vec = Vec::new(); //获取上传id
let multipart_upload_res: CreateMultipartUploadOutput = self
.client
.create_multipart_upload()
.bucket("bucket")
.key("/tmp/key")
.send()
.await.unwrap();
let upload_id = match multipart_upload_res.upload_id() {
Some(id) => id,
None => {
return Err(anyhow!("upload id is None"));
}
}; //分段上传文件并记录completer_part
loop {
let mut buf = vec![0; chuck_size];
let read_count = file.read(&mut buf)?;
part_number += 1; if read_count == 0 {
break;
} let body = &buf[..read_count];
let stream = ByteStream::from(body.to_vec()); let upload_part_res = self
.client
.upload_part()
.key(key)
.bucket(bucket)
.upload_id(upload_id)
.body(stream)
.part_number(part_number)
.send()
.await.unwrap(); let completer_part = CompletedPart::builder()
.e_tag(upload_part_res.e_tag.unwrap_or_default())
.part_number(part_number)
.build(); upload_parts.push(completer_part); if read_count != chuck_size {
break;
}
}
// 完成上传文件合并
let completed_multipart_upload: CompletedMultipartUpload =
CompletedMultipartUpload::builder()
.set_parts(Some(upload_parts))
.build(); let _complete_multipart_upload_res = self
.client
.complete_multipart_upload()
.bucket("bucket")
.key(key)
.multipart_upload(completed_multipart_upload)
.upload_id(upload_id)
.send()
.await.unwrap();

有时候面对大文件,比如几百兆甚至几个G的文件,为了节约带宽和内存,我才采取分段上传的方案,然后在对象存储的服务端做合并。基本流程是:指定bucket和key,获取一个上传id;按流读取文件,分段上传字节流,并记录CompletedPart;通知服务器按照CompletedPart 集合来合并文件。具体过程代码已加注释,这里不再累述。

大文件下载

let mut file = match OpenOptions::new()
.truncate(true)
.create(true)
.write(true)
.open("/tmp/target_file");
let key = "/tmp/test/key".to_string();
let resp = client
.get_object()
.bucket("bucket")
.key(&key)
.send()
.await.unwrap(); let content_len = resp.content_length();
let mut byte_stream_async_reader = resp.body.into_async_read();
let mut content_len_usize: usize = content_len.try_into().unwrap();
loop {
if content_len_usize > chunk_size {
let mut buffer = vec![0; chunk_size];
let _ = byte_stream_async_reader.read_exact(&mut buffer).await.unwrap();
file.write_all(&buffer).unwrap();
content_len_usize -= chunk_size;
continue;
} else {
let mut buffer = vec![0; content_len_usize];
let _ = byte_stream_async_reader.read_exact(&mut buffer).await.unwrap();
file.write_all(&buffer).unwrap();
break;
}
}
file.flush().unwrap();

在从对象存储服务端下载文件的过程中也会遇到大文件问题。为了节约带宽和内存,我们采取读取字节流的方式分段写入文件。首先get_object()函数获取ByteStream,通过async_reader流式读取对象字节,分段写入文件。

对象存储的相关话题今天先聊到这儿,下期见。

文盘Rust -- rust连接oss的更多相关文章

  1. RUST actix-web连接有密码的Redis数据库

    RUST actix-web连接有密码的Redis数据库 actix-web的example里面,使用了自己的actix-redis,但是我尝试了一下,并不好用 替换成另一连接池,deadpool-r ...

  2. 文盘Rust -- rust 连接云上数仓 starwift

    作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...

  3. 文盘Rust -- 本地库引发的依赖冲突

    作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...

  4. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  5. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  6. 文盘Rust -- 给程序加个日志

    作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...

  7. 文盘Rust -- 用Tokio实现简易任务池

    作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...

  8. 利用jsoup爬取百度网盘资源分享连接(多线程)

    突然有一天就想说能不能用某种方法把百度网盘上分享的资源连接抓取下来,于是就动手了.知乎上有人说过最好的方法就是http://pan.baidu.com/wap抓取,一看果然链接后面的uk值是一串数字, ...

  9. Rust: move和borrow

    感觉Rust官方的学习文档里关于ownship,borrow和lifetime介绍的太简略了,无法真正理解这些语法设计的原因以及如何使用(特别是lifetime).所以找了一些相关的blog来看,总结 ...

  10. [追热点]Rust学习资源整理

    为什么选择Rust 在一次演讲中,谈到微软为解决相应内存问题所做的工作,微软研究人员 Matthew Parkinson 提到了微软正在开发的基于 Rust 的新编程语言 Verona. 摘自:[Ru ...

随机推荐

  1. gradle设置

    本地目录: gradle-wrapper.properties distributionUrl=file\:///D:/\.gradle/gradle-7.3-all.zip distribution ...

  2. 随便记录一些使用IDEA在ssm阶段的踩过的坑

    重命名中括号问题:需要重命名模块+目录 Intellij idea 报错:Error : java 不支持发行版本5_灵颖桥人的博客-CSDN博客_不支持发行版本5 idea中的目标字节码版本总是自动 ...

  3. 动力节点的MySQL的34题目的第7题的我的参考答案

    以下是:薪水的平均等级最低的部门的名称 select t4.t4deptno,t4.t4grade,d1.dname from( ##求出各部门平均等级begin select avg(t3.t3gr ...

  4. DASCTF NOV X联合出题人-PWN

    太忙了,下午4点才开始做,,剩下的以后补上 签个到 逻辑很简单两个功能的堆,一个就是申请heap.还有一个是检验如果校验通过就会得到flag 申请模块 ​ 中间0x886是个很恶心的东西,需要我们绕过 ...

  5. 04-Spring中的AOP编程之基于xml开发

    AOP编程 ​ AOP为Aspect Oriented Programming的缩写,意为:面向切面编程.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的 ...

  6. 利用 Rainbond 云原生平台简化 Kubernetes 业务问题排查

    Kubernetes 已经成为了云原生时代基础设施的事实标准,越来越多的应用系统在 Kubernetes 环境中运行.Kubernetes 已经依靠其强大的自动化运维能力解决了业务系统的大多数运行维护 ...

  7. GO实现Redis:GO实现Redis协议解析器(2)

    本文实现Redis的协议层,协议层负责解析指令,然后将指令交给核心database执行 echo database用来测试协议层的代码 https://github.com/csgopher/go-r ...

  8. 前端ffmpeg实现视频剪切

    利用ffmpeg实现纯前端视频剪切 注意:在新版本Chrome浏览器中由于安全性问题,只能在https或localhost当中才能正常使用 1. 下载ffmpeg npm install @ffmpe ...

  9. 一些随笔No.3

    1.开发应以业务为导向,技术只是手段 2.视觉上和程序上不一定是完全符合 比如,我所说的阻塞是视觉层面,或者是对用户而言的阻塞,而不是程序意义上的.我也许会传完参的同时销毁原组件,生成一个看起来一模一 ...

  10. 有一个公网IP地址

    这几天在家里拉了一条300M+的宽带,但是遇到了一些坑,本文就简单说明一下如下: 突发此次需求是这样的:阿里云有台服务器公网带宽是1M的,虽说带宽小,但是数据中心的服务器显然是稳定的,只是带宽太小,有 ...