作者:京东科技 贾世闻

对象存储是云的基础组件之一,各大云厂商都有相关产品。这里跟大家介绍一下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. How to Apply WebLogic Server (WLS) Patches Using Smart Update

    本文目的: 描述weblogic10.3.6及之前的版本,如何通过Smart Update打补丁 先决条件: You should download and apply the enhanced Sm ...

  2. 阿里云CentOS7 下部属nginx+uwsgi+python3+django

    安装依赖包 yum -y groupinstall "Development tools" yum install openssl-devel bzip2-devel expat- ...

  3. Go_day05

    Go基础语法 OOP面向对象 Go语言本身不是面向对象的语言,但是可以通过一些方法来模拟对象 面向对象的思维就是分类思维 继承 // 定义一个父类结构体 type Person struct { na ...

  4. python数据结构转字符串_python2中字符不显示问题_python2_递归

    # encoding:utf-8 def get_str(data): """将python数据转化为肉眼可见的字符串 :param data: str.dict.lis ...

  5. 我用 Laf 三分钟写了一个专属 ChatGPT ,Laf 创始人:明天来上班!

    起因 故事是这样的,一个月黑风高的夜晚,我掏出手机像往常一样打开朋友圈. 一开始我是不相信的,直到我(快速的) --> 打开 laf --> 创建应用 --> 新建云函数 --> ...

  6. Day05笔记

    01.数组类(了解) 1.目的:设计一个类,该类有数组的功能,可以存储数据,可以删除修改数据 2.设计核心数据 1.属性:指针(指向堆区空间),数组实际存储的元素个数,数组容量 2.方法:构造(开辟堆 ...

  7. Serial port

    前言 ​ 使用qt开发一款简易串口助手. ​ 目标: 1. 实现正常串口通信. 2. 能够传输AT指令.(需要注意回车符) github仓库地址:shadow-wd/Serial-port-assis ...

  8. ArcGIS Pro处理发布并在前端调用bim数据全过程-rvt转slpk

    记录ArcGIS处理三维bim模型全纪录,从原始的rvt格式开始,到最后web前端js api调用的整个过程,并记录部分中间操作过程中出现的问题和解决办法. 本文示例使用: 软件:ArcGIS Pro ...

  9. C++库封装JNI接口——实现java调用c++

    1. JNI原理概述 通常为了更加灵活高效地实现计算逻辑,我们一般使用C/C++实现,编译为动态库,并为其设置C接口和C++接口.用C++实现的一个库其实是一个或多个类的简单编译链接产物.然后暴露其实 ...

  10. kubernetes核心实战(九)

    14.Ingress 检查是否有安装 [root@k8s-master-node1 ~/yaml/test]# kubectl get pod,svc -n ingress-nginx NAME RE ...