Как правильно загрузить большое тело запроса коа на AWS S3?

1

Я создаю бэкэнд приложения. Клиенты отправляют файл как тело запроса на сервер, затем сервер загружает файл в AWS S3. Сервер использует NodeJS и веб-фреймворк KOA.

Если я использую raw-body чтобы получить тело сообщения в буфер, когда файл большой, буфер большой и вызывает ошибку нехватки памяти.
Если я непосредственно передаю ctx.req (объект IncomingMessage) в S3.putObject, AWS SDK выдает ошибку " Cannot determine length of [object Object] удается Cannot determine length of [object Object], похоже, что AWS SDK пытается получить длину потока, а затем начинает загрузку из нескольких частей.

AWS SDK версия 2.383.0 (в настоящее время последняя)
NodeJS 10.14.2

В настоящее время я написал функцию, которая читает из IncomingMessage как поток, ждет события данных для заполнения большого буфера (16 МБ), а затем выполняет загрузку нескольких частей на S3, это хорошо решает проблему, но я все еще ищу для лучшего решения.

  • 0
    пожалуйста, проверьте этот medium.com/@stephenjwatkins/from-the-brows3
  • 0
    @user1665355 user1665355 спасибо за комментарий, и по соображениям производительности я попробовал это решение, но все равно не подходит для моей ситуации.
Теги:
amazon-s3
aws-sdk
koa

1 ответ

0
Лучший ответ

После нескольких месяцев, я думаю, мое окончательное решение является стабильным и надежным.

Основной концепцией является прием из хранилища потока IncomingMessage в буфер, после того как буфер достигнет размера, затем поместит текущую часть в S3, затем продолжит чтение потока до конца.

const uploaderLogger = Log4js.getLogger('customUploader');
function customMultiPartUpload(s3, bucket, key, incomingMessage, partSizeInByte) {
    return new Promise((resolve) => {
        partSizeInByte = partSizeInByte || uploadBufferMB * 1024 * 1024;
        uploaderLogger.debug('part size is ${partSizeInByte}');

        let uploadId = null;
        let partNumber = 0;
        let parts = [];
        let fileSize = 0;
        let reserveBuffer = Buffer.alloc(0);
        const sendBuffer = Buffer.alloc(partSizeInByte);
        const md5Hash = Crypto.createHash('md5');

        const doUpload = async (uploadBuffer) => {
            if (!uploadId) {
                uploaderLogger.debug('multipart upload not initialized');
                const createData = await s3.createMultipartUpload({
                    Bucket: bucket,
                    Key: key
                }).promise();
                uploadId = createData.UploadId;
                uploaderLogger.debug('uploadId ${uploadId}');

                partNumber = 0;
            }
            fileSize += uploadBuffer.length;
            uploaderLogger.debug('buffer length ${uploadBuffer.length}, total ${fileSize}');

            partNumber += 1;
            uploaderLogger.debug('part number ${partNumber}');

            md5Hash.update(uploadBuffer);

            const partData = await s3.uploadPart({
                Bucket: bucket,
                Key: key,
                PartNumber: partNumber,
                UploadId: uploadId,
                Body: uploadBuffer
            }).promise();
            parts.push({
                PartNumber: partNumber,
                ETag: partData.ETag
            });
            uploaderLogger.debug('etag ${partData.ETag}');
        };

        incomingMessage.on('data', async (chunkBuffer) => {
            incomingMessage.pause();

            reserveBuffer = Buffer.concat([ reserveBuffer, chunkBuffer ]);
            if (reserveBuffer.length > partSizeInByte) {
                do {
                    reserveBuffer.copy(sendBuffer, 0, 0, partSizeInByte);
                    reserveBuffer = reserveBuffer.slice(partSizeInByte);
                    await doUpload(sendBuffer);
                } while (reserveBuffer.length > partSizeInByte);
            }

            incomingMessage.resume();
        });

        incomingMessage.on('end', async () => {
            uploaderLogger.debug('stream end');

            if (reserveBuffer.length > 0) {
                await doUpload(reserveBuffer);
            }

            if (uploadId) {
                uploaderLogger.debug('uploadId not null');
                await s3.completeMultipartUpload({
                    Bucket: bucket,
                    Key: key,
                    UploadId: uploadId,
                    MultipartUpload: {
                        Parts: parts
                    }
                }).promise();
                uploaderLogger.debug('multipart upload complete');
            }

            const hash = md5Hash.digest('hex');

            resolve({
                size: fileSize,
                hash: hash
            });
            uploaderLogger.debug('return file size ${fileSize}, hash ${hash}');
        });
    });
}

Отрегулируйте partSizeInByte в соответствии с использованием памяти вашего сервера. Слишком большой размер детали может привести к OOM, когда сервер обрабатывает много запросов, слишком малый размер детали может быть меньше ограничения части S3.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню