Skip to content

Repository files navigation

.NET CoreNuGetNuGetcodecovCodeFactor

Клиент для S3

Привет! Это обертка над HttpClient для работы с S3 хранилищами. Мотивация создания была простейшей - я не понимал, почему клиенты AWS (4.0.0) и Minio (6.0.4) потребляют так много памяти. Результат экспериментов: скорость почти как у AWS, а потребление памяти почти в 150 раз меньше, чем клиент для Minio (и в 17 для AWS).

BenchmarkDotNet v0.14.0, Debian GNU/Linux 12 (bookworm) (container)
Intel Xeon CPU E5-2697 v3 2.60GHz, 1 CPU, 56 logical and 28 physical cores
.NET SDK 8.0.408
[Host] : .NET 8.0.15 (8.0.1525.16413), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.15 (8.0.1525.16413), X64 RyuJIT AVX2
Job=.NET 8.0 Runtime=.NET 8.0
MethodMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
Aws5.788 s0.0297 s0.0264 s1.050.02100025.78 MB17.14
Minio7.016 s0.0824 s0.0730 s1.270.03-274.03 MB182.13
Storage5.510 s0.1063 s0.0994 s1.000.02-1.5 MB1.00

Создание клиента

Для работы с хранилищем необходимо создать клиент.

varstorageClient=newS3Client(newS3Settings{AccessKey="ROOTUSER",Bucket="mybucket",EndPoint="localhost",// для Yandex.Objects это "storage.yandexcloud.net"Port=9000,// стандартный порт Minio - 9000, для Yandex.Objects указывать не нужноSecretKey="ChangeMe123",UseHttps=false,// для Yandex.Objects укажите trueUseHttp2=false// Yandex.Objects позволяет работать по HTTP2, можете указать true})

Также, в конструктор клиента можно передать имплементацию интерфейса IArrayPool, которая позволяет тонко настроить переиспользование массивов клиента.

Minio предоставляет playground для тестирования (порт для запросов всё тот же - 9000). Ключи можно найти в документации. Доступ к Amazon S3 не тестировался.

Операции с S3 bucket

Создание bucket'a

Мы передаём название bucket'a в настройках, поэтому дополнительно его вводить не надо.

boolbucketCreateResult=awaitstorageClient.CreateBucket(cancellationToken);Console.WriteLine(bucketCreateResult?"Bucket создан":"Bucket не был создан");

Проверка существования bucket'a

Как и в прошлый раз, мы знаем название bucket'a, так как мы передаём его в настройках клиента.

boolbucketCheckResult=awaitstorageClient.IsBucketExists(cancellationToken);if(bucketCheckResult)Console.WriteLine("Bucket существует");

Удаление bucket'a

boolbucketDeleteResult=awaitstorageClient.DeleteBucket(cancellationToken);if(bucketDeleteResult)Console.WriteLine("Bucket удалён");

Операции с S3 object

Напомню, что объект в смысле S3 это и есть файл.

Создание файла

Создание, то есть загрузка файла в S3 хранилище, возможна двумя путями: можно разбить исходные данных на кусочки ( multipart), а можно не разбивать. Самый простой способ загрузки файла - воспользоваться следующим методом (если файл будет больше 5 МБ, то применяется multipart):

boolfileUploadResult=awaitstorageClient.UploadFile(fileName,fileContentType,fileStream,cancellationToken);if(fileUploadResult)Console.WriteLine("Файл загружен");

Управление Multipart-загрузкой

Для самостоятельного управления multipart-загрузкой, можно воспользоваться методом UploadFile без указания данных. Получится примеоно такой код:

usingS3Uploadupload=awaitstorageClient.UploadFile(fileName,fileType,cancellationToken);awaitupload.AddParts(stream,cancellationToken);// загружаем части документаif(!awaitupload.AddParts(byteArray,cancellationToken)){// загружаем другую часть документаawaitupload.Abort(cancellationToken);// отменяем загрузку}else{awaitupload.Complete(cancellationToken);// завершаем загрузку}

В коде клиента именно эту логику использует метод PutFileMultipart. Конкретную реализацию можно подсмотреть в нём.

Получение файла

StorageFilefileGetResult=awaitstorageClient.GetFile(fileName,cancellationToken);if(fileGetResult){Console.WriteLine($"Размер файла {fileGetResult.Length}, контент {fileGetResult.ContetType}");returnawaitfileGetResult.GetStream(cancellationToken);}else{Console.WriteLine($"Файл не может быть загружен, так как {fileGetResult}");}

Получение файла как Stream

varfileStream=awaitstorageClient.GetFileStream(fileName,cancellationToken);

В случае, если файл не существует, возвратится Stream.Null.

Проверка существования файла

boolfileExistsResult=awaitstorageClient.IsFileExists(fileName,cancellationToken);if(fileExistsResult){Console.WriteLine("Файл существует");}

Создание подписанной ссылки на файл

Метод проверяет наличие файла в хранилище S3 и формирует GET запрос файла. Параметр expiration должен содержать время валидности ссылки начиная с даты формирования ссылки.

string?preSignedFileUrl=storageClient.GetFileUrl(fileName,expiration);if(preSignedFileUrl!=null){Console.WriteLine($"URL получен: {preSignedFileUrl}");}

Существует не безопасный способ создать ссылку, без проверки наличия файла в S3.

stringpreSignedFileUrl=awaitstorageClient.BuildFileUrl(fileName,expiration,cancellationToken);

Удаление

Удаление объекта из S3 происходит почти мгновенно. На самом деле в S3 хранилище просто ставится задача на удаление и клиенту возвращается результат. Кстати, если удалить файл, который не существует, то ответ будет такой же, как если бы файл существовал. Поэтому этот метод ничего не возвращает.

awaitstorageClient.DeleteFile(fileName,cancellationToken);Console.WriteLine("Файл удалён, если он, конечно, существовал");

Измерение производительности и тестирование

Локальное измерение производительности и тестирование осуществляется с помощью Minio в Docker'e по http. Понимаю, что это не самый хороший способ, но зато он самый доступный и простой.

  1. Файл docker-compose для локального тестирования можно найти в репозитории.
  2. Запускаем docker-compose up -d. Если всё хорошо, то бенчмарк заработает в Docker'e.
  3. Если нужно запустить бенчмарк локально, то обращаем внимание на файл appsettings.json. В нём содержатся основные настройки для подключения к Minio.
  4. Свойство BigFilePath файла appsettings.json сейчас не заполнено. Его можно использовать для загрузки реального файла (больше 100МБ). Если свойство не заполнено, то тест сгенерирует случайную последовательность байт размером 123МБ в памяти.

Вопросы

У меня есть канал в TG: @csharp_gepard. К нему привязан чат - вопросы можно задавать в чате, либо в любом из последних постов.

Releases

Packages

Used by

Contributors

Languages