我正在使用React和Nodejs。我们有scenerio,我们必须在AWS上上传10GB的文件。我知道这不可能与单个请求,这就是为什么我把它分成块,但它仍然不工作,并抛出错误后,几个块上传。以下是代码
前端
import React, { useState, useEffect } from "react";
import { UploadDropzone } from "../../../components/icons";
import { Progress } from "antd";
import Dropzone from "react-dropzone";
import axios from "axios";
import { notification } from "antd";
import { isEmptyArray } from "formik";
const chunkSize = 1048576 * 25;
function UploadLocalVideo(props) {
const { setUploadUrl, storageId, acceptFileType, isResourceBucket, callBackFun } = props;
const [progress, setProgress] = useState(0);
const [beginingOfTheChunk, setBeginingOfTheChunk] = useState(0);
const [endOfTheChunk, setEndOfTheChunk] = useState(chunkSize);
const [fileToBeUpload, setFileToBeUpload] = useState({});
const [progressUpload, setProgressUpload] = useState(0);
const [fileSize, setFileSize] = useState(0);
const [chunkCount, setChunkCount] = useState(0);
const [uploadId, setUploadId] = useState("");
const [name, setName] = useState("");
const [parts, setParts] = useState([]);
const [counter, setCounter] = useState(1);
const [percent, setPercent] = useState(0);
const [fileType, setFileType] = useState("");
const [uploading, setUploading] = useState(false);
const onUpload = async (files) => {
if (!isEmptyArray(files)) {
setUploading(true);
}
let percent = 0;
let name = Math.random().toString(36).substring(2, 10);
resetChunkProperties();
const _file = files[0];
name = (name + _file?.name?.replace(/ +/g, "")).replace(/[{()}]/g, "");
setName(name);
setFileType(_file.type);
setFileSize(_file.size);
const _totalCount = _file.size % chunkSize == 0 ? _file.size / chunkSize : Math.floor(_file.size / chunkSize) + 1;
setChunkCount(_totalCount);
percent = 100 / _totalCount;
setPercent(percent);
setFileToBeUpload(_file);
setProgress(1);
};
const resetChunkProperties = () => {
setProgressUpload(0);
setCounter(1);
setBeginingOfTheChunk(0);
setEndOfTheChunk(chunkSize);
setUploadId("");
setName("");
setParts([]);
setPercent(0);
setProgress(0);
setFileType("");
};
useEffect(() => {
if (fileSize > 0) {
fileUpload(counter);
}
}, [fileToBeUpload, progressUpload]);
const fileUpload = () => {
setCounter(counter + 1);
if (counter <= chunkCount) {
var chunk = fileToBeUpload.slice(beginingOfTheChunk, endOfTheChunk);
uploadChunk(chunk);
}
};
const uploadChunk = async (chunk) => {
try {
const formData = new FormData();
formData.append("file", chunk);
formData.append("name", name);
formData.append("fileType", fileType);
formData.append("chunkSize", chunk.size);
formData.append("currentIndex", counter);
formData.append("totalChunk", chunkCount);
formData.append("uploadId", uploadId);
formData.append("EtagArray", JSON.stringify(parts));
formData.append("storageId", storageId);
formData.append("isResourceBucket", isResourceBucket);
await axios({
method: "post",
url: `${process.env.REACT_APP_NEW_API_HOSTNAME}/upload-chunk`,
data: formData,
}).then((response) => {
if (response.data.uploadStatus == "uploading") {
setBeginingOfTheChunk(endOfTheChunk);
setEndOfTheChunk(endOfTheChunk + chunkSize);
setUploadId(response.data.uploadId);
setParts([...parts, response.data.etag]);
setProgress(parseInt((progressUpload + 1) * percent));
setProgressUpload(progressUpload + 1);
} else if (response.data.uploadStatus == "complete") {
setUploadUrl(response.data.url); // set url or response url
callBackFun(fileToBeUpload);
setProgress(100);
setUploading(false);
} else if (response.data.uploadStatus == "failed" || response.data.status == false) {
notification["error"]({ message: response.data.message });
setProgress(0);
setUploading(false);
} else if (response.data.success == false) {
notification["error"]({ message: "Storage not found" });
setProgress(0);
setUploading(false);
}
});
} catch (error) {
console.log(error, "error");
}
};
return (
<div className="form-group">
<Dropzone
onDrop={(acceptedFiles) => {
onUpload(acceptedFiles);
}}
accept={acceptFileType}
disabled={uploading}
>
{({ getRootProps, getInputProps }) => (
<div className="dropzone">
<div className="dropzone-inner" {...getRootProps()}>
<input {...getInputProps()} />
<div className="dropzone-icon">
<UploadDropzone />
</div>
<div className="dropzone-title">Upload a File</div>
<div className="dropzone-subtitle">
Click to <u>browse</u>, or drag & drop your file here
</div>
<Progress strokeLinecap="butt" type="line" percent={progress} /> {progress > 1 ? `${progress} %` : ""}
</div>
</div>
)}
</Dropzone>
</div>
);
}
export default UploadLocalVideo;
后端
const handler = async (request, reply) => {
try {
let uploadId = (_.get(request.payload, "uploadId", ""));
let fileName = (_.get(request.payload, "name", ""));
let multiParts = JSON.parse(_.get(request.payload, "EtagArray", []))
let storageId = _.get(request, "payload.storageId", "")
let dataBuffer = Buffer.from(request.payload.file)
let currentChunkIndex = parseInt(request.payload.currentIndex);
let totalChunk = parseInt(request.payload.totalChunk);
let isResourceBucket = JSON.parse(_.get(request, "payload.isResourceBucket", false))
let region = ""
let credentials = {}
let squery = {
name: { $in: ["aws"] }
}
if (isResourceBucket && storageId == "") {
squery._id = mongoose.Types.ObjectId("62e112750e3d4dada1b9a3c0")
} else {
squery._id = mongoose.Types.ObjectId(storageId)
}
if (currentChunkIndex <= totalChunk) {
let storages = await Storage.findOne(squery)
if (storages && storages.credentials) {
credentials = await StreamService.decrypt_storage_credentials(storages.credentials, 'aws')
region = (credentials.region).replace("s3.", "").replace(".amazonaws.com", "").replace("s3-", "")
} else {
return reply({
status: false,
message: 'Storage not found',
uploadStatus: "failed"
})
}
}
AWS.config.update({
accessKeyId: credentials.access_key,
secretAccessKey: credentials.secret_key,
region: region
})
const s3 = new AWS.S3({
params: {
Bucket: credentials.bucket_name,
},
// endpoint,
signatureVersion: 'v4',
region: region,
apiVersion: '2006-03-01'
})
let filename = `uploadFile/${fileName}`;
if (currentChunkIndex == 1) {
uploadId = await getUploadId(filename, s3, credentials.bucket_name)
console.log("currentChunkIndex", " == ", currentChunkIndex, { uploadId })
}
if (currentChunkIndex < totalChunk) {
let etag = await uploadParts(filename, credentials.bucket_name, dataBuffer, currentChunkIndex, uploadId, s3)
return reply({
status: true,
uploadId,
etag,
uploadStatus: "uploading",
message: "uploading"
})
} else if (currentChunkIndex == totalChunk) {
let finalChunk = await uploadParts(filename, credentials.bucket_name, dataBuffer, currentChunkIndex, uploadId, s3)
let etag = { ETag: finalChunk.ETag, PartNumber: currentChunkIndex };
multiParts.push(etag)
let location = await completeFileMultiPartUpload(filename, credentials.bucket_name, uploadId, multiParts, s3, credentials.cdn_suffix);
location = location.replace("%2F","/")
console.log({ location })
if (location) {
return reply({
status: true,
url: location,
uploadStatus: "complete",
message: "upload completed"
})
}
}
} catch (error) {
logger.error(error)
return reply({
success: false,
message: error.message
});
}
};
这些是日志
所以我有几个问题。1.当我们上传10GB的文件时,它是否会消耗节点内存?如果“是”,那么解决方案是什么?
谢谢
2条答案
按热度按时间ocebsuys1#
AWS Lambda不支持有效负载大小大于6mb的调用。
您可以在AWS Lambda documentation页面的"功能配置、部署和执行"下查看此限制和其他限制。另外,请注意AWS S3对多部分上传有自己的限制,如您在AWS S3 documentation页面所见。
从你的前端代码看,你的文件上传块似乎被设置为每个25mb;这远远超过了该极限。
回答你的第一个问题:是的,当您通过Lambda将文件数据上传到S3时,上传的文件数据被分配到函数存储器上-尽管这似乎不是问题所在。
一个可能的解决方案是将块大小设置为接近5mb(~
1048576 * 5
)--但不精确到6mb,因为http请求数据也计入大小限制。最后,从最佳实践的Angular 来看,AWS Lambda不太适合文件上传,特别是大文件。亚马逊通过S3 pre-signed urls支持并鼓励将文件直接上传到AWS S3。lambda函数只负责为每个块生成预签名的URL,并将其响应到前端,这也会从本质上降低计算成本。
您可以从AWS本身查看这个amazing blog post,描述如何实现这一点。
ttp71kqs2#
节点的堆内存
如果在块到达时不将上载流的块流式传输到服务器上的设备存储,您将只能上载适合Node堆内存的文件。这里的“上载流”是Node和/或Express中的
request
对象,它是可读流。有关如何增加堆内存的链接可以在Web上找到,但我在www.example.com站点上找不到明确的信息node.org。* 您还需要在服务器上提供超过10 Gb的可用内存。*
从前端上传表单数据
在研究这个主题时,我发现最常见的解决方法是将文件作为
formData
对象的一部分上传,在上传大文件时,仍然需要将文件内容流传输到服务器上的磁盘。将文件数据作为请求正文发送
一个不太常见的解决方案是使用
fetch
(或XHTTPRequest
对象)将文件作为请求主体上传。上传的数据仍需要流式传输到服务器上的本地存储。此方法不适用于14.1之前的Node版本,尽管将request
流动态流式传输到磁盘,但该版本仍会耗尽堆空间(更新Node修复了此问题)。将文件作为正文中的二进制数据直接上载还需要在前端向
fetch
提供一个相当复杂的init
对象,包括Content Disposition
作为文件名,Content-type
表示文件类型,Last-modiied
表示修改日期,并在本地实现mine"Payload-Length
,以八位字节为单位,用于在发送请求主体之前显示文件大小,以实现错误控制和安全目的。注意这个答案是针对文章中关于Node的1号和2号问题的-没有提到AWS或React,我声称没有经验。也有可能发布的错误是在内存耗尽之前发生的。