AWS-C++上传5GB以上S3文件速度极慢且失败问题排查
问题描述
Windows环境下基于AWS-C++ SDK开发的S3上传代码,在上传小于5GB的文件时速度正常且能完成;但上传5GB以上大文件时,速度仅约0.4MB/s,且持续报错Error: PutObject: Encountered network error when sending http request。浏览器上传同一份大文件速度正常,存储桶已开启传输加速,但不清楚如何在SDK客户端配置该功能,需排查文件大小差异导致异常的原因。
核心原因分析
- 单请求上传的硬限制:S3的
PutObject接口单请求最大仅支持5GB文件上传,超过该阈值后,SDK虽会尝试处理,但单请求传输大文件会导致内存占用过高、网络超时风险陡增,直接引发速度暴跌和网络错误。 - 未启用分段上传:AWS-C++ SDK默认不会自动对大文件启用分段上传,而分段上传是S3处理大文件的标准方案,能通过并发传输提升速度、降低超时概率。
- 传输加速未配置:存储桶已开启传输加速,但客户端未对应配置,无法利用AWS全球加速节点优化大文件传输路径。
- Windows文件流效率问题:代码中使用的
Aws::FStream在Windows环境下处理超大文件时,可能存在读取效率瓶颈,间接拖慢上传速度。
解决方案
1. 改用TransferManager处理大文件上传
AWS-C++ SDK的TransferManager是专门针对大文件传输的组件,会自动对超过阈值的文件启用分段上传,默认阈值为8MB,还支持自动重试、并发传输分段,大幅提升大文件上传的速度和稳定性。
2. 配置S3传输加速
在客户端配置中开启useAccelerateEndpoint参数,配合已开启加速的存储桶,让客户端自动连接到S3的全球加速节点。
3. 优化客户端与文件读取配置
- 调整超时时间:大文件分段上传需要更长的超时窗口,建议将
requestTimeoutMs设为300000毫秒(5分钟)、connectTimeoutMs设为60000毫秒(1分钟)。 - 提升并发数:在TransferManager中设置
maxUploadConcurrency(如5),同时可根据网络带宽调整分段大小(如16MB/段)。 - 优化文件读取:Windows环境下可通过调整文件流缓冲区大小提升读取效率。
修改后的代码示例
FileHandler.h
#ifndef BEASTBURST_FILEHANDLER_H #define BEASTBURST_FILEHANDLER_H #include <iostream> #include <fstream> #include <aws/core/Aws.h> #include <aws/s3/S3Client.h> #include <aws/transfer/TransferManager.h> #include <aws/core/utils/memory/stl/AWSStreamFwd.h> namespace BeastBurstCLI { class FileHandler { public: FileHandler(); ~FileHandler(); bool UploadFile(const Aws::String &bucketName, const Aws::String &filePath, const Aws::String &destinationPath); private: Aws::SDKOptions options; std::shared_ptr<Aws::Client::ClientConfiguration> config_; std::shared_ptr<Aws::Transfer::TransferManager> transferManager_; std::mutex gMutex; bool gUploadComplete = false; long long gBytesSent = 0; long long gTotalBytes = 0; std::chrono::steady_clock::time_point startTime; void OnTransferProgress(const Aws::Transfer::TransferManager*, const Aws::Transfer::TransferProgress& progress); }; } #endif //BEASTBURST_FILEHANDLER_H
FileHandler.cpp
#include "FileHandler.h" #include <iostream> #include <iomanip> #include <aws/s3/model/ServerSideEncryption.h> BeastBurstCLI::FileHandler::FileHandler() : options{} { options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace; Aws::InitAPI(options); // 初始化客户端配置,启用传输加速 config_ = std::make_shared<Aws::Client::ClientConfiguration>(); config_->region = Aws::Region::EU_CENTRAL_1; config_->useAccelerateEndpoint = true; // 调整超时时间适配大文件上传 config_->requestTimeoutMs = 300000; // 5分钟 config_->connectTimeoutMs = 60000; // 1分钟 // 创建TransferManager配置,设置并发数和分段大小 Aws::Transfer::TransferManagerConfiguration tmConfig; tmConfig.s3Client = Aws::MakeShared<Aws::S3::S3Client>("S3Client", config_); tmConfig.maxUploadConcurrency = 5; // 并发上传5个分段 tmConfig.uploadChunkSize = 16 * 1024 * 1024; // 每个分段16MB,可根据网络调整 transferManager_ = Aws::Transfer::TransferManager::Create(tmConfig); } BeastBurstCLI::FileHandler::~FileHandler() { Aws::ShutdownAPI(options); } void BeastBurstCLI::FileHandler::OnTransferProgress(const Aws::Transfer::TransferManager*, const Aws::Transfer::TransferProgress& progress) { std::lock_guard<std::mutex> lock(gMutex); gBytesSent = progress.GetBytesTransferred(); } bool BeastBurstCLI::FileHandler::UploadFile(const Aws::String &bucketName, const Aws::String &filePath, const Aws::String &destinationPath) { gUploadComplete = false; gBytesSent = 0; // 获取文件大小 std::ifstream file(filePath, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "无法打开文件: " << filePath << std::endl; return false; } gTotalBytes = file.tellg(); file.close(); // 构造上传请求 Aws::Transfer::TransferRequest request; request.SetBucket(bucketName); Aws::String fileName = Aws::Utils::StringUtils::Split(filePath, '/').back(); Aws::String objectKey = destinationPath + "/" + fileName; request.SetKey(objectKey); request.SetFilePath(filePath); // 设置服务器端加密 request.SetServerSideEncryption(Aws::S3::Model::ServerSideEncryption::AES256); // 设置进度回调 auto progressCallback = [this](const Aws::Transfer::TransferManager* tm, const Aws::Transfer::TransferProgress& progress) { OnTransferProgress(tm, progress); }; request.SetProgressCallback(progressCallback); startTime = std::chrono::steady_clock::now(); // 启动进度显示线程 std::thread progressThread([this]() { while (!gUploadComplete) { std::this_thread::sleep_for(std::chrono::seconds(2)); std::lock_guard<std::mutex> lock(gMutex); double progressMB = gBytesSent / (1024.0 * 1024.0); double totalMB = gTotalBytes / (1024.0 * 1024.0); std::chrono::steady_clock::time_point currentTime = std::chrono::steady_clock::now(); std::chrono::duration<double> elapsedSeconds = currentTime - startTime; double uploadSpeed = progressMB / elapsedSeconds.count(); if (gTotalBytes > 0 && progressMB > 0) { std::cout << "进度: " << (int)(progressMB * 100.0 / totalMB) << "% (" << (int)progressMB << "MB / " << (int)totalMB << "MB), "; std::cout << "速度: " << std::fixed << std::setprecision(2) << uploadSpeed << " MB/s" << std::endl; } } }); // 执行上传 auto future = transferManager_->UploadFile(request); auto outcome = future.get(); gUploadComplete = true; progressThread.join(); if (!outcome.IsSuccess()) { std::cerr << "上传失败: " << outcome.GetError().GetMessage() << std::endl; return false; } else { std::cout << "文件 '" << fileName << "' 已成功上传到存储桶 '" << bucketName << "' 的路径 '" << destinationPath << "'。" << std::endl; return true; } }
额外注意事项
- 确保使用最新版本的AWS-C++ SDK,旧版本可能在TransferManager或传输加速支持上存在缺陷。
- 检查IAM权限,确保上传账号拥有
s3:PutObject及s3:PutObjectTagging(分段上传必需)的权限。 - 若网络环境不稳定,可在TransferManager中配置重试策略,增加重试次数提升容错性。
内容的提问来源于stack exchange,提问作者Venelin
相关产品推荐
相关产品推荐

