rusty-cat × Azure Blob 直传直下实战(Shared Key 版)
本文是 rusty-cat 分片并发文档 的「云存储实战」系列之一,教你用 账号 Shared Key 直连 的方式,把大文件分片并发上传到 Azure Blob Storage、并从 Blob 下载。 先读概念请看:《分片并发上传指南》、《分片并发下载指南》。 需要的 feature:
azure-blob-direct。
1. 适用场景与密钥模型
「直传(direct)」= 客户端进程直接持有 Azure 存储账号的账号密钥(Account Key,Base64),自己在本地做 Shared Key 签名。
🖼️ 【配图占位 1|direct 密钥模型】 建议配图:可信服务器持一把钥匙(Account Key),直接连 Azure Blob;旁注不可信环境(手机/浏览器)打叉、请改用 SAS。 临时 ASCII 草图:
[可信服务器 🔑AccountKey] ──Shared Key 签名──▶ [Azure Blob] [用户手机/浏览器 ❌] 请改用「SAS」方案
- ✅ 适合:跑在你完全掌控的可信服务器 / 后端。
- ❌ 不适合:不可信环境请改用 《Azure Blob SAS 实战》。
2. 加依赖
[dependencies]
rusty-cat = { version = "0.2.4", features = ["azure-blob-direct"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
3. 上传:完整可运行程序
use std::sync::Arc;
use rusty_cat::api::{FileTransferRecord, MeowClient, MeowConfig, UploadPounceBuilder};
use rusty_cat::azure_blob_direct::AzureBlobDirectUpload;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = MeowClient::new(MeowConfig::default());
// 账号名、Base64 的账号密钥
let protocol = AzureBlobDirectUpload::new("myaccount", "BASE64_ACCOUNT_KEY");
let task = UploadPounceBuilder::new(
"blob.bin",
"./blob.bin",
1024 * 1024, // 1 MiB 分片
)
.with_url("https://myaccount.blob.core.windows.net/mycontainer/blob.bin")
.with_breakpoint_upload(Arc::new(protocol))
.with_max_parts_in_flight(4) // 单文件 4 片并发
.build()?;
let outcome = client
.enqueue_and_wait(task, |r: FileTransferRecord| {
println!("上传进度 {:.1}% 状态 {:?}", r.progress() * 100.0, r.status());
})
.await?;
println!("上传完成!payload={:?}", outcome.payload);
client.close().await?;
Ok(())
}
内部发生了什么
Azure Block Blob 的模型是「先分块上传、再一次性提交块列表」:
🖼️ 【配图占位 2|Azure Block Blob 并发上传数据流】 建议配图:客户端并发发送 4 个
Put Block(每个 block id 是分片下标的 base64);全部完成后发一次Put Block List按下标严格排序提交。图注:提交顺序来自 block-id 列表,不是到达顺序;Put Block不返回 ETag。 临时 ASCII 草图:客户端 ═══Put Block #0═══▶ ┐ ═══Put Block #1═══▶ ├─ 最多 4 块并行(block id = base64(下标)) ═══Put Block #2═══▶ │ ═══Put Block #3═══▶ ┘ ──Put Block List(按下标排序)──▶ Azure 提交合并 ✅
- 每个 block id 是分片下标的纯函数,
Put Block List固定提交顺序,所以并发安全; - Azure 最多 50000 个 block、单块最大 4000 MiB;
- 峰值内存 ≈
n × 分片大小。
4. 下载:两种方式
方式一(串行):direct 下载协议
use std::sync::Arc;
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
use rusty_cat::azure_blob_direct::AzureBlobDirectDownload;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = MeowClient::new(MeowConfig::default());
let protocol = AzureBlobDirectDownload::new("myaccount", "BASE64_ACCOUNT_KEY");
let task = DownloadPounceBuilder::new(
"blob.bin",
"./downloads/blob.bin",
1024 * 1024,
"https://myaccount.blob.core.windows.net/mycontainer/blob.bin",
)
.with_breakpoint_download(Arc::new(protocol)) // 挂 provider 协议 = 串行
.build();
client.enqueue_and_wait(task, |_r| {}).await?;
client.close().await?;
Ok(())
}
方式二(推荐,可并发):SAS GET URL + 默认下载器
想并发下载 Blob,就让后端签一个 SAS GET URL,不挂 provider 协议、直接喂默认下载器:
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let sas_get_url = "https://myaccount.blob.core.windows.net/mycontainer/blob.bin?sv=...&sig=...";
let total_size: u64 = 20 * 1024 * 1024;
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new("blob.bin", "./downloads/blob.bin", 2 * 1024 * 1024, sas_get_url)
.with_total_size(total_size)
.with_max_parts_in_flight(4)
.build();
client.enqueue_and_wait(task, |r| println!("{:.1}%", r.progress() * 100.0)).await?;
client.close().await?;
Ok(())
}
如果某个严格的 Azure 网关要求显式
x-ms-version等头,用任务构建器的with_headers(...)加进去(会同时作用于HEAD和 rangeGET),绝不要去改签名 URL。并发下载完整规则见 《分片并发下载指南》。签 SAS 的细节见 《Azure Blob SAS 实战》。
5. 断点续传与清理
- 续传:Azure 没有 upload id,续传靠恢复服务端的「未提交 block 列表」。重启后重建任务再
try_enqueue即可。 - 未提交 block 会计费,直到 Azure 垃圾回收(约 7 天)。给容器配一条生命周期规则清理废弃 block。
🖼️ 【配图占位 3|未提交 block 的续传与回收】 建议配图:中断后服务端留有若干「未提交 block」;续传时接管这些 block、只补缺失的;未被提交的 block 约 7 天后被 GC。
6. 进度回调与任务控制(暂停 / 恢复 / 取消)
前面用的 enqueue_and_wait 会一直等到任务结束,适合脚本或一次性任务。做 UI、或要在后台并发跑并随时控制任务时,改用底层的 try_enqueue:它立刻返回 TaskId,让你分别处理进度回调和完成回调。把上文构建好的 task 传进去即可:
use rusty_cat::api::{
FileTransferRecord, MeowClient, MeowError, PounceTask, TaskId, TransferStatus,
};
// task 按上文「上传」或「下载」小节构建好后传进来
async fn enqueue_with_control(
client: &MeowClient,
task: PounceTask,
) -> Result<TaskId, MeowError> {
let task_id = client
.try_enqueue(
task,
// 进度回调:每次进度更新都会调用
move |record: FileTransferRecord| match record.status() {
TransferStatus::Failed(err) => eprintln!("失败:{err}"),
TransferStatus::Canceled => println!("已取消"),
_ => println!("进度 {:.1}%", record.progress() * 100.0),
},
// 完成回调:成功到达 Complete 时调用一次
move |task_id, payload| println!("任务 {task_id} 完成 {payload:?}"),
)
.await?;
Ok(task_id)
}
拿到 TaskId 后就能随时控制任务:
client.pause(task_id).await?; // 暂停
client.resume(task_id).await?; // 从断点继续(TaskId 不变)
client.cancel(task_id).await?; // 取消(Azure 会停止累积未提交的 block)
record.status()返回&TransferStatus,取值:Pending/Transmission/Paused/Complete/Failed(err)/Canceled;进度是0.0~1.0的比例(f32)。完整语义见《分片并发上传指南》第 8 节与《分片并发下载指南》第 9 节。
7. 相关阅读
- 《分片并发上传指南》、《分片并发下载指南》 — 通用机制与避坑。
- 《Azure Blob SAS 实战》 — 密钥留后端的更安全方案。
- 文档总索引:doc/README.md。
本文基于
rusty-cat0.2.4,代码片段均经过cargo check --features all编译验证。