Отправка больших файлов с использованием azure-sdk-for-java с ограниченной кучей

Мы разрабатываем микрослужбу документов, которая должна использовать Azure в качестве хранилища файлового содержимого. Azure Block Blob казался разумным выбором. Служба документов имеет размер кучи, ограниченный 512 МБ (-Xmx512m).

Мне не удалось заставить потоковую загрузку файлов с ограниченной кучей работать с использованием azure-storage-blob:12.10.0-beta.1 (также проверено на 12.9.0).

Были предприняты следующие подходы:

  1. Скопируйте-вставьте из документации с помощью 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 пытается использовать отметку/сброс, хотя поток ввода файла сообщает, что эта функция не поддерживается.

  1. Добавление 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 ГБ содержимого файла в память.

  1. Замена 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

  1. Переключиться на трансляцию через 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 подтверждает, что содержимое файла было повреждено во время загрузки. Занимаемая память значительно уменьшилась, но повреждение файлов стало препятствием.

Проблема: не удается найти работающее решение для загрузки/выгрузки с небольшим объемом памяти.

Любая помощь будет принята с благодарностью!

«Файл поврежден во время загрузки. Веб-портал Azure показывает 1,09 ГБ вместо ожидаемых 1,17 ГБ», может быть, веб-портал Azure показывает Гибибайт / ГиБ (т. е. 1024³ байт) вместо гигабайт (т. е. 1000³ байт)? Потому что 1,17 ГБ ≈ 1,09 ГБ. (Хотя, если вы локально подтвердили, что загруженный файл поврежден, это может быть не ответ)

Marcono1234 21.12.2020 18:23

@ Marcono1234 Marcono1234 да, я проверил, что размер исходного и загруженного файлов в байтах одинаков. Так что по всем параметрам вы правы. Однако сам файл поврежден (например, загруженное изображение имеет 60% серых пикселей, загруженное видео не воспроизводится). Я использовал этот фрагмент, чтобы убедиться, что размер байта одинаков (а также проверить его вручную с помощью загрузки и сравнения): log.info("EXPECTED SIZE: {}; ACTUAL SIZE: {}", image.length(), blockBlobClient.getProperties().getBlobSize()); Создал этот запрос в GitHub для проверки работоспособности: github.com/Azure/azure-sdk-for-java /вопросы/18295

white-sagittarius 21.12.2020 20:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
8
2
3 572
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Пожалуйста, попробуйте приведенный ниже код для загрузки/выгрузки больших файлов, я протестировал на своей стороне, используя файл .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.

white-sagittarius 22.12.2020 09:05

кстати, я заметил, что параллельно загружается/скачивается всего 6 файлов. Если я работаю с более чем 6 файлами параллельно, остальные ждут завершения первых 6. Вы случайно не знаете, есть ли настройка для управления этим?

white-sagittarius 22.12.2020 10:58

Мое предположение о том, что существует какое-то скрытое ограничение до 6 файлов, загружаемых/выгружаемых одновременно, было нелепым, поскольку я обнаружил, что это связано с настройками Google Chrome – я использовал Swagger для вызова конечных точек загрузки/выгрузки (Chrome имеет ограничение в 6 подключений на имя хоста и максимум 10 подключений)

white-sagittarius 23.12.2020 14:28

@white-sagittarius, спасибо за этот совет, я не был так знаком с этим раньше

Stanley Gong 24.12.2020 08:45

@StanleyGong Что здесь делает setMaxConcurrency?

Gaurav 04.01.2021 18:06

Другие вопросы по теме