Storage
Storage copied to clipboard
Simple client for S3-storage
Клиент для S3
Привет! Это обертка над HttpClient для работы с S3 хранилищами. Мотивация создания была простейшей - я не понимал, почему клиенты AWS и Minio едят так много памяти . Результат моих экспериментов: скорость почти как у Minio, а памяти потребляю почти в 200 раз меньше, чем клиент для AWS. На Windows. На Alpine и Debian (если запустить бенчмарк в контейнере) результаты сильно скромнее - в 7 раз меньше памяти, чем клиент на AWS. Интересно, кстати, почему.
BenchmarkDotNet = v0.13.5, OS=Windows 11 (10.0.22621.1265/22H2/2022Update/SunValley2)
AMD Ryzen 7 5800H with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK = 7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 DEBUG
.NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job = .NET 7.0 Runtime=.NET 7.0
| Method | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| Aws | 2.173 s | 1.73 | 25000.0000 | 8000.0000 | 207 341.8 KB | 252.99 |
| Minio | 1.365 s | 1.08 | - | - | 279 989.3 KB | 341.64 |
| Storage | 1.282 s | 1.00 | - | - | 819.5 KB | 1.00 |
Создание клиента
Для работы с хранилищем необходимо создать клиент.
var storageClient = new S3Client(new S3Settings
{
AccessKey = "ROOTUSER",
Bucket = "mybucket",
EndPoint = "localhost", // для Yandex.Objects это "storage.yandexcloud.net"
Port = 9000, // стандартный порт Minio - 9000, для Yandex.Objects указывать не нужно
SecretKey = "ChangeMe123",
UseHttps = false, // для Yandex.Objects укажите true
UseHttp2 = false // Yandex.Objects позволяет работать по HTTP2, можете указать true
})
Minio предоставляет playground для тестирования (порт для запросов всё тот же - 9000). Ключи можно найти в документации. Доступ к Amazon S3 не тестировался.
Операции с S3 bucket
Создание bucket'a
Мы передаём название bucket'a в настройках, поэтому дополнительно его вводить не надо.
bool bucketCreateResult = await storageClient.CreateBucket(cancellationToken);
Console.WriteLine(bucketCreateResult
? "Bucket создан"
: $"Bucket не был создан");
Проверка существования bucket'a
Как и в прошлый раз, мы знаем название bucket'a, так как мы передаём его в настройках клиента.
bool bucketCheckResult = await storageClient.IsBucketExists(cancellationToken);
if (bucketCheckResult) Console.WriteLine("Bucket существует");
Удаление bucket'a
bool bucketDeleteResult = await storageClient.DeleteBucket(cancellationToken);
if (bucketDeleteResult) Console.WriteLine("Bucket удалён");
Операции с S3 object
Напомню, что объект в смысле S3 это и есть файл.
Создание файла
Создание, то есть загрузка файла в S3 хранилище, возможна двумя путями: можно разбить исходные данных на кусочки ( multipart), а можно не разбивать. Самый простой способ загрузки файла - воспользоваться следующим методом (если файл будет больше 5 МБ, то применяется multipart):
bool fileUploadResult = await storageClient.UploadFile(fileName, fileContentType, fileStream, cancellationToken);
if (fileUploadResult) Console.WriteLine("Файл загружен");
Управление Multipart-загрузкой
Для самостоятельного управления multipart-загрузкой, можно воспользоваться методом UploadFile без указания данных. Получится примеоно такой код:
using S3Upload upload = await storageClient.UploadFile(fileName, fileType, cancellationToken);
await upload.Upload(stream, cancellationToken); // загружаем часть документа
if (!await upload.Upload(byteArray, cancellationToken)) { // загружаем другую часть документа
await upload.Abort(cancellationToken); // отменяем загрузку
}
else {
await upload.Complete(cancellationToken); // завершаем загрузку
}
В коде клиента именно эту логику использует метод PutFileMultipart. Конкретную реализацию можно подсмотреть в нём.
Получение файла
StorageFile fileGetResult = await storageClient.GetFile(fileName, cancellationToken);
if (fileGetResult) {
Console.WriteLine($"Размер файла {fileGetResult.Length}, контент {fileGetResult.ContetType}");
return await fileGetResult.GetStream(cancellationToken);
}
else {
Console.WriteLine($"Файл не может быть загружен, так как {fileGetResult}");
}
Проверка существования файла
bool fileExistsResult = await storageClient.IsFileExists(fileName, cancellationToken);
if (fileExistsResult) Console.WriteLine("Файл существует");
Создание подписанной ссылки на файл
Метод проверяет наличие файла в хранилище S3 и формирует GET запрос файла. Параметр expiration должен содержать время
валидности ссылки начиная с даты формирования ссылки.
string? preSignedFileUrl = storageClient.GetFileUrl(fileName, expiration);
if (preSignedFileUrl != null) Console.WriteLine($"URL получен: {preSignedFileUrl}");
Существует не безопасный способ создать ссылку, без проверки наличия файла в S3.
string preSignedFileUrl = await storageClient.BuildFileUrl(fileName, expiration, cancellationToken);
Удаление
Удаление объекта из S3 происходит почти мгновенно. На самом деле в S3 хранилище просто ставится задача на удаление и клиенту возвращается результат. Кстати, если удалить файл, который не существует, то ответ будет такой же, как если бы файл существовал. Поэтому этот метод ничего не возвращает.
await storageClient.DeleteFile(fileName, cancellationToken);
Console.WriteLine("Файл удалён, если он, конечно, существовал");
Измерение производительности и тестирование
Локальное измерение производительности и тестирование осуществляется с помощью Minio в Docker'e по http. Понимаю, что это не самый хороший способ, но зато он самый доступный и простой.
- Файл
docker-composeдля локального тестирования можно найти в репозитории. - Запускаем
docker-compose up -d. Если всё хорошо, то бенчмарк заработает в Docker'e. - Если нужно запустить бенчмарк локально, то обращаем внимание на файл
appsettings.json. В нём содержатся основные настройки для подключения к Minio. - Свойство
BigFilePathфайлаappsettings.jsonсейчас не заполнено. Его можно использвоать для загрузки реального файла (больше 100МБ). Если свойство не заполнено, то тест сгенерирует случайную последовательность байт размером 123МБ в памяти.