Мы разрабатываем микрослужбу документов, которая должна использовать Azure в качестве хранилища файлового содержимого. Azure Block Blob казался разумным выбором. Служба документов имеет размер кучи, ограниченный 512 МБ (-Xmx512m
).
Мне не удалось заставить потоковую загрузку файлов с ограниченной кучей работать с использованием azure-storage-blob:12.10.0-beta.1
(также проверено на 12.9.0
).
Были предприняты следующие подходы:
BlockBlobClient
BlockBlobClient blockBlobClient = blobContainerClient.getBlobClient("file").getBlockBlobClient();
File file = new File("file");
try (InputStream dataStream = new FileInputStream(file)) {
blockBlobClient.upload(dataStream, file.length(), true /* overwrite file */);
}
Результат: java.io.IOException: mark/reset not supported
- SDK пытается использовать отметку/сброс, хотя поток ввода файла сообщает, что эта функция не поддерживается.
BufferedInputStream
для смягчения проблемы с пометкой/сбросом (по совету ):BlockBlobClient blockBlobClient = blobContainerClient.getBlobClient("file").getBlockBlobClient();
File file = new File("file");
try (InputStream dataStream = new BufferedInputStream(new FileInputStream(file))) {
blockBlobClient.upload(dataStream, file.length(), true /* overwrite file */);
}
Результат: java.lang.OutOfMemoryError: Java heap space
. Я предполагаю, что SDK попытался загрузить все 1,17 ГБ содержимого файла в память.
BlockBlobClient
на BlobClient
и снятие ограничения на размер кучи (-Xmx512m
):BlobClient blobClient = blobContainerClient.getBlobClient("file");
File file = new File("file");
try (InputStream dataStream = new FileInputStream(file)) {
blobClient.upload(dataStream, file.length(), true /* overwrite file */);
}
Результат: использовано 1,5 Гб динамической памяти, все содержимое файла загружается в память + некоторая буферизация на стороне Reactor
Использование кучи от VisualVM
BlobOutputStream
:long blockSize = DataSize.ofMegabytes(4L).toBytes();
BlockBlobClient blockBlobClient = blobContainerClient.getBlobClient("file").getBlockBlobClient();
// create / erase blob
blockBlobClient.commitBlockList(List.of(), true);
BlockBlobOutputStreamOptions options = (new BlockBlobOutputStreamOptions()).setParallelTransferOptions(
(new ParallelTransferOptions()).setBlockSizeLong(blockSize).setMaxConcurrency(1).setMaxSingleUploadSizeLong(blockSize));
try (InputStream is = new FileInputStream("file")) {
try (OutputStream os = blockBlobClient.getBlobOutputStream(options)) {
IOUtils.copy(is, os); // uses 8KB buffer
}
}
Результат: файл поврежден во время загрузки. Веб-портал Azure показывает 1,09 ГБ вместо ожидаемых 1,17 ГБ. Загрузка файла вручную с веб-портала Azure подтверждает, что содержимое файла было повреждено во время загрузки. Занимаемая память значительно уменьшилась, но повреждение файлов стало препятствием.
Проблема: не удается найти работающее решение для загрузки/выгрузки с небольшим объемом памяти.
Любая помощь будет принята с благодарностью!
@ Marcono1234 Marcono1234 да, я проверил, что размер исходного и загруженного файлов в байтах одинаков. Так что по всем параметрам вы правы. Однако сам файл поврежден (например, загруженное изображение имеет 60% серых пикселей, загруженное видео не воспроизводится). Я использовал этот фрагмент, чтобы убедиться, что размер байта одинаков (а также проверить его вручную с помощью загрузки и сравнения): log.info("EXPECTED SIZE: {}; ACTUAL SIZE: {}", image.length(), blockBlobClient.getProperties().getBlobSize());
Создал этот запрос в GitHub для проверки работоспособности: github.com/Azure/azure-sdk-for-java /вопросы/18295
Пожалуйста, попробуйте приведенный ниже код для загрузки/выгрузки больших файлов, я протестировал на своей стороне, используя файл .zip размером около 1,1 ГБ.
Для загрузки файлов:
public static void uploadFilesByChunk() {
String connString = "<conn str>";
String containerName = "<container name>";
String blobName = "UploadOne.zip";
String filePath = "D:/temp/" + blobName;
BlobServiceClient client = new BlobServiceClientBuilder().connectionString(connString).buildClient();
BlobClient blobClient = client.getBlobContainerClient(containerName).getBlobClient(blobName);
long blockSize = 2 * 1024 * 1024; //2MB
ParallelTransferOptions parallelTransferOptions = new ParallelTransferOptions()
.setBlockSizeLong(blockSize).setMaxConcurrency(2)
.setProgressReceiver(new ProgressReceiver() {
@Override
public void reportProgress(long bytesTransferred) {
System.out.println("uploaded:" + bytesTransferred);
}
});
BlobHttpHeaders headers = new BlobHttpHeaders().setContentLanguage("en-US").setContentType("binary");
blobClient.uploadFromFile(filePath, parallelTransferOptions, headers, null, AccessTier.HOT,
new BlobRequestConditions(), Duration.ofMinutes(30));
}
Объем памяти:
Для скачивания файлов:
public static void downLoadFilesByChunk() {
String connString = "<conn str>";
String containerName = "<container name>";
String blobName = "UploadOne.zip";
String filePath = "D:/temp/" + "DownloadOne.zip";
BlobServiceClient client = new BlobServiceClientBuilder().connectionString(connString).buildClient();
BlobClient blobClient = client.getBlobContainerClient(containerName).getBlobClient(blobName);
long blockSize = 2 * 1024 * 1024;
com.azure.storage.common.ParallelTransferOptions parallelTransferOptions = new com.azure.storage.common.ParallelTransferOptions()
.setBlockSizeLong(blockSize).setMaxConcurrency(2)
.setProgressReceiver(new com.azure.storage.common.ProgressReceiver() {
@Override
public void reportProgress(long bytesTransferred) {
System.out.println("dowloaded:" + bytesTransferred);
}
});
BlobDownloadToFileOptions options = new BlobDownloadToFileOptions(filePath)
.setParallelTransferOptions(parallelTransferOptions);
blobClient.downloadToFileWithResponse(options, Duration.ofMinutes(30), null);
}
Объем памяти:
Результат:
Спасибо вам за быстрый ответ! Я попробовал ваш подход, и он отлично сработал. Вот скриншот использования кучи во время загрузки/выгрузки. Я считаю, что использование файлов позволяет Azure SDK пропустить довольно много шагов копирования/буферизации. Файл не поврежден. Единственное небольшое неудобство заключается в том, что мы получаем наши данные из сети в виде InputStream
и должны записывать их во временный файл, чтобы использовать uploadFromFile
API.
кстати, я заметил, что параллельно загружается/скачивается всего 6 файлов. Если я работаю с более чем 6 файлами параллельно, остальные ждут завершения первых 6. Вы случайно не знаете, есть ли настройка для управления этим?
Мое предположение о том, что существует какое-то скрытое ограничение до 6 файлов, загружаемых/выгружаемых одновременно, было нелепым, поскольку я обнаружил, что это связано с настройками Google Chrome – я использовал Swagger для вызова конечных точек загрузки/выгрузки (Chrome имеет ограничение в 6 подключений на имя хоста и максимум 10 подключений)
@white-sagittarius, спасибо за этот совет, я не был так знаком с этим раньше
@StanleyGong Что здесь делает setMaxConcurrency
?
«Файл поврежден во время загрузки. Веб-портал Azure показывает 1,09 ГБ вместо ожидаемых 1,17 ГБ», может быть, веб-портал Azure показывает Гибибайт / ГиБ (т. е. 1024³ байт) вместо гигабайт (т. е. 1000³ байт)? Потому что 1,17 ГБ ≈ 1,09 ГБ. (Хотя, если вы локально подтвердили, что загруженный файл поврежден, это может быть не ответ)