[ELMA3] Распределенное файловое хранилище
Термины и определения
Точка хранения контента (ТХК) – сетевой сервис, обслуживающий локализованное хранение, закачку, отдачу и прочие манипуляции с контентом файлов.
Системная точка хранения контента – это ближайшая к серверу / ферме серверов ELMA точка хранения контента. Близость определяется не физическим расстоянием от сервера ELMA до сервера, где работают сервисы точки хранения контента, а сетевыми задержками при коммуникации: чем меньше задержка, тем ближе узлы друг к другу.
Ключ контента – уникальный идентификатор хранимого контента.
Местоположение контента – ссылка, уникально указывающая на место хранения контента. В себе содержит ключ контента и уникальный идентификатор точки хранения контента.
Первоисточник контента – точка хранения контента, куда он был впервые загружен.
Контекст местоположения пользователя – сведения о пользователе и его сетевом подключении, которые позволяют определить ближайшую к нему точку хранения контента.
Концепция и топология распределённого хранения
Большие компании как правило имеют распределённую сеть офисов в разных географических точках. В таких условиях инсталляция ELMA находится в какой-то одной точке и все файлы, соответственно, находятся там же. С точки зрения конечного пользователя такой большой компании хранение всех файлов в одном месте невыгодно из-за появления сетевых задержек при доставке контента в удалённые офисы.
Данное изменение добавляет провайдер распределённого хранения файлов в систему, который оперирует точками хранения контента. Провайдер может содержать одну или несколько точек хранения. При работе все операции над контентом файлов делегируются им.
Возвращаясь к примеру с большой компанией, каждый крупный офис имеет свою точку хранения с минимальными сетевыми задержками для всех пользователей в офисе. Если учесть, что большинство файлов, загруженных на точку хранения, востребованы только внутри своего офиса, то запросы к этим файлам будут обработаны с максимально быстро. Если приходит запрос на файл из другого офиса, то только первый запрос получит заметно большую задержку, вызванную доставкой контента с удалённой точки хранения. После того, как контент файла был доставлен на местную точку хранения, он начинает раздаваться максимально быстро.
Сервер ELMA или ферма серверов тоже имеет собственную точку хранения. Она считается системной. Файл попадает в системную точку хранения, если он создаётся системой, либо если не удалось определить ближайшую к пользователю точку.
Топология распределённого хранения:
- сервер или ферма серверов ELMA – в случае фермы все узлы находятся рядом, никакого разнесения по географически разным точкам не предусмотрено.
- сервис файлового шлюза – предоставляет интерфейс (Web API) для манипуляций с контентом файлов для серверов ELMA. Является частью точки хранения контента.
- сервис хранилища объектов – обрабатывает запросы на закачку, скачивание и получение метаданных контента от сервиса файлового шлюза и только от него, следит за политикой удаления старых временных файлов. Может быть интегрирован в сервис файлового шлюза. Является частью точки хранения контента.
- клиент – браузер или мобильное приложение ELMA.
Точка хранения контента
Точка хранения контента состоит из двух основных служб – файловый шлюз и хранилище объектов.
Файловый шлюз – это WebAPI служба, которая принимает и обрабатывает запросы клиентов. Все обращения к контенту шлюз передаёт в хранилище объектов.
Хранилище объектов – это служба хранения контента. Она обрабатывает запросы только от файлового шлюза. Предоставляет набор базовых операций над файлами шлюзу.
Хранение контента
Хранимый в точке хранения контент делится на 3 вида:
- временный;
- постоянный;
- предпросмотр.
Каждый вид размещается в своей папке. На временный контент действует политика удаления старых временных файлов.
Поддерживаемые файловым шлюзом команды
Файловый шлюз поддерживает следующие команды:
- скачивание контента файла - скачивает постоянно хранимый или временный контент;
- размещение контента файла на локальной точке хранения (длительная задача) - точка хранения скачивает контент из первоисточника и размещает его у себя локально;
- шифрование контента файла (длительная задачи) - шифрует и заменяет оригинальный контент, находящийся в папке постоянного хранения;
- дешифрование контента файла (длительная задачи) - дешифрует зашифрованный контент во временный файл;
- проверка статуса длительной задачи - проверка статуса и возврат результатов выполнения длительной задачи;
- сохранение контента - превращает временный контент в постоянно хранимый. Временный контент может быть удалён;
- закачка контента файла - сохраняет отправленный от клиента контент во временный файл.
Распространение контента
В распределённом хранении один и тот же контент может находиться как на одной, так и на нескольких точках хранения.
Когда клиент закачивает файл, он закачивает его как временный файл на свою ближайшую точку хранения. В случае, если она не была назначена или её не удалось определить, контент будет закачан в системную точку хранения.
При сохранении файла провайдер даёт команду перенести его в другое место хранилища, и после этого файл становится постоянно хранимым. В этот момент в БД делается запись, что файл N находится в точке хранения X и эта точка становится первоисточником контента N.
При запросе контента клиент всегда направляется на свою ближайшую точку хранения. Если там этот контент есть и его целостность в порядке, то он отдаётся сразу с ближайшей точки хранения. Но если там контента не оказалось или проверка целостности завершилась неудачей, то точка хранения обращается к первоисточнику за ним. Закачав контент к себе, ближайшая точка записывает его в свою постоянную часть хранилища, и отдаёт клиенту.
Способ выбора ближайшей точки хранения контента
При работе с распределённым провайдером возникает и такое понятие, как "Контекст местоположения пользователя". Это такие сведения, как:
- идентификатор пользователя (на основании него можно создать индивидуальное сопоставление "пользователь => точка хранения");
- организационная единица, которой принадлежит пользователь (тоже сопоставление, но уже массовое);
- IP адрес узла, с которого было установлено сетевое соединение (сопоставление по сетевым подсетям – применимо только в локальных сетях);
- долгота и широта (применимо для мобильных устройств);
- другое.
Этапы определения ближайшей точки в порядке приоритета:
- Индивидуальное сопоставление "пользователь => точка хранения".
- Массовое сопоставление через точку расширения IVicinityContextHandler. Точка расширения получает контекст запроса и имеет доступ ко всем переданным данным. В настоящий момент ещё не существует ни одного компонента для массового сопоставления.
- Сопоставление с системной точкой хранения.
Поиск ведётся до первого сработавшего обработчика. Сопоставление с системной точкой хранения всегда срабатывает.
Обеспечение безопасности
Для того, чтобы устранить возможность несанкционированного доступа к хранимым файлам, некоторые запросы между участниками экосистемы ELMA защищаются с помощью криптографии. Дополнительно каждый защищённый запрос имеет ограниченное время жизни, которое устанавливается в настройках точки хранения контента.
Передаваемые аргументы сериализуются в json и снабжаются датой создания запроса. Этот пакет данных подписывается закрытым ключом сервера и вместе с подписью снова упаковывается в json, который уже преобразуется в строку с кодировкой Base64 и отправляется в качестве параметра запроса. Если в передаваемых аргументах запроса передаётся пароль шифрования к файлу, то он шифруется асимметричным алгоритмом RSA.
Детали реализации и изменения
В распределённом хранении местоположение контента уже выходит за границы локальной файловой системы, поэтому потребовалась какая-то новая система для указания места размещения контента. В качестве новой системы был выбран формат URI, который поддерживается классом Uri в C#. Формат поддерживает как ссылки на локальную файловую систему, так и на некий удалённый ресурс.
Вот шаблон ссылки на локальные файлы:
file:///drive:/folder1/folder2/filename.ext
где:
file – наименование схемы,
drive: - буква диска,
folderN – имена папок,
filename.ext – имя файла с расширением.
Сетевые ресурсы вида \\sever\share также поддерживаются: file://server/folder1/folder2/filename.ext
В качестве ссылки на местоположения в распределённом провайдере реализована следующая схема:
elma://gateway/folder/path
где:
elma – это наименование схемы (как http),
gateway – имя шлюза,
folder – корневая папка (temp – для временных файлов, files – для постоянно хранимых, previews – для файлов предпросмотра),
path – путь к контенту внутри корневой папки.
Для хранения местоположения поля ContentFilePath в классе BinaryFile стало недостаточно, оно было помечено устаревшим и добавлено новое поле ContentLocation с типом Uri. Новое свойство можно установить только при создании объекта BinaryFile, поменять значение свойства после создания пользователь уже не может (а провайдер может). Устаревшее свойство ContentFilePath стало производным от нового свойства ContentLocation.
Штатный провайдер был подвергнут рефакторингу и все места, где было обращение к локальной файловой системе, сначала были вынесены в виртуальные protected методы, а затем перешли в отдельный провайдер операций с хранилищем с интерфейсом IStorageOperationsProvider.
Также в ходе рефакторинга был выделен небольшой сервис:
IFileGatewayService – сервис файловых шлюзов, оперирует ближайшими шлюзами (установка индивидуального сопоставление, определение ближайшего) и т.п.
Распределённый провайдер является наследником штатного, но создаёт свой провайдер операций и на запросы загрузки файлов выдаёт свой прокси к BinaryFile. Провайдер операций распределённого хранения реализует взаимодействие с точками хранения. Распределённый провайдер предоставляет свою реализацию для сервиса IFileGatewayService, а также добавляет новые сервисы:
- IUploadUrlGenerator – сервис генерации адресов закачки (Upload) через шлюзы. Результат используется для настройки Html5Uploader, используемого в контролах для передачи файлов.
- IDownloadUrlService – служба формирования ссылки для скачивания контента файла. Используется для редиректа клиента к точке хранения (компонент DistributedBinaryFileDownloadHandler).
Доступ к контенту BinaryFile-ов
Доступ к контенту файла через свойство ContentFilePath поддерживается из соображений совместимости: когда обращаются к этому свойству, то содержимое файла выкачивается из точки хранения и размещается во временном файле на локальной файловой системе.
Основной способ доступа к контенту BinaryFile теперь – это потоки. В связи с этим в системе были переделаны почти все места, где шло обращение через ContentFilePath. Потоки доступны через сервис IBinaryFileStreams.
Создание BinaryFile-ов
Как и прежде, доступен прежний способ создания файлов, когда вызывается конструктор и устанавливается свойство ContentFilePath.
var file = new BinaryFile
{
ContentFilePath = @"C:\Path\to\my-file.pdf",
// тут остальные свойства
};
Появился конструктор, принимающий местоположение контента, но его область применения преимущественно только в провайдерах.
BinaryFileBuilder
Предпочтительным вариантом создания файла с точки зрения клиента провайдера считаю BinaryFileBuilder: есть возможность установить часто используемые свойства, передать контент в виде файла, потока или массива байт или ссылки на местоположение. Работает и в сценариях.
Приведу класс билдера, который расскажет о его возможностях:
public class BinaryFileBuilder
{
public BinaryFileBuilder Uid(Guid uid);
public BinaryFileBuilder Name(string name);
public BinaryFileBuilder ContentType(string contentType);
public BinaryFileBuilder Created(DateTime createDate);
public BinaryFileBuilder Content(byte[] contentBytes);
public BinaryFileBuilder Content(string contentFilePath, bool deleteContentFileAfterSave = false);
public BinaryFileBuilder Content(Stream contentStream, bool autoCloseStream = false);
public BinaryFileBuilder Content(Uri contentLocation, long contentLength, uint contentCrc);
public BinaryFile Build();
}
public BinaryFileBuilder Content(Uri contentLocation, long contentLength, uint contentCrc);
public BinaryFile Build();
}
Билдер вызывается через методы BinaryFile.CreateNew() и BinaryFile.CreateFrom(). Второй метод позволяет использовать существующий BinaryFile в качестве шаблона для свойств (но контент в эти свойства не входит).
Пример:
return BinaryFile.CreateNew()
.Name(SR.T("Штрих-код.bmp"))
.ContentType("image/bmp")
.Content(bitmapStream)
.Build();
Конфигурация и развёртывание
Требования к окружению
ПО точки хранения базируется на .NET Core 2.1 (LTS), поэтому для его работы обязательно требуется установить ASP.NET Core/.NET Core: Runtime & Hosting Bundle, доступный по ссылке https://dotnet.microsoft.com/download/dotnet-core/2.1
Основной раздел по способам размещения приложений .NET Core 2.1 находится по ссылке https://docs.microsoft.com/ru-ru/aspnet/core/host-and-deploy/?view=aspnetcore-2.1
ПО точки хранения можно и просто запустить на встроенном сервере Kestrel, как обыкновенное консольное приложение, но такой способ не годится для production развёртывания:
dotnet EleWise.ELMA.FileGateway.dll [--urls=http://0.0.0.0:<port>]
где <port> - номер порта, на котором сервис точки хранения будет принимать запросы от клиентов. По-умолчанию, 5000.
Утилита для генерации пары ключей
Для настройки распределённого хранения серверу и каждой точке хранения требуется пара ключей RSA: закрытый ключ всегда остаётся на узле, открытый ключ передаётся другим участникам.
Вместе с ПО точки хранения поставляется небольшая консольная утилита для создания пары ключей RSA. Создать ключи можно вызвав команду
create:
dotnet EleWise.ELMA.FileGateway.RsaKeyUtil.dll create
Эта строка создаст пару ключей размером 1024 бита и сохранит их в файлы private-key.xml и public-key.xml.
У команды create есть опции:
-p, --prefix Префикс у имени ключей, например {prefix}.public-key.xml.
-s, --size (Значение по-умолчанию: 1024) Размер ключа RSA от 384 до 16384. Значение должно делиться на 8 без остатка.
-f, --force (Значение по-умолчанию: false) Принудительно перезаписать существующие файлы ключей.
Настройка сервера
Для настройки ELMA в режиме распределённого хранения необходимо обновить конфигурацию файлового провайдера:
- Создать пару ключей RSA для сервера.
- Добавить дескриптор настроек распределённого хранения, добавив тег в секцию <configSections>…</configSections>:
<section name="distributedStorageSettings" type="EleWise.ELMA.FileProvider.Distributed.Configuration.DistributedStorageSettings, EleWise.ELMA.FileProvider.Distributed" />
- Заменить тип файлового провайдера (содержит отсылку на закрытый ключ) на:
<add name="FSProvider"
type="EleWise.ELMA.FileProvider.Distributed.DistributedFileStoreProvider, EleWise.ELMA.FileProvider.Distributed"
privateKeyPath="D:\Elma\Keys\server.private-key.xml" />
- В конец файла config перед закрывающим тегом </configuration> добавить секции настроек распределённого хранения:
<distributedStorageSettings>
<fileGatewayList>
<!-- Сюда будут добавляться настройки точек хранения -->
</fileGatewayList>
</distributedStorageSettings>
Добавление очередной точки хранения
Для добавления очередной точки хранения следует сделать шаги:
- Развернуть ПО точки хранения контента. Произвести настройку провайдера хранилища.
- При помощи утилиты сгенерировать пару открытый-закрытый ключ RSA для шлюза (public-key.xml, private-key.xml).
- Положить закрытый ключ шлюза в папку с ключами.
- В конфигурации шлюза сослаться на закрытый ключ шлюза (путь "Security:GatewayPrivateKeyFilePath").
- Положить открытый ключ сервера в папку с ключами.
- В конфигурации шлюза сослаться на открытый ключ сервера (путь "Security:ServerPublicKeyFilePath").
- Открытый ключ шлюза перенести на сервер ELMA и положить в папку с конфигурацией.
- В конфигурации configсервера добавить точку хранения, указав имя, адрес и сослаться на файл открытого ключа:
<fileGateway name="central
endpointUrl="http://central-gw.example.com:8080"
publicKeyFile="D:\Elma\Keys\central-gw.public-key.xml" />
Скачивание BinaryFile-ов
В базовый контроллер ControllerBase была добавлена перегрузка метода File, которая принимает BinaryFile. Теперь при реализации методов контроллера, которые возвращают контент BinaryFile, нет необходимости вручную получать поток, путь или массив с контентом. Перегрузка всё сделает сама. Для удалённых файлов она вернёт редирект на соответствующую точку хранения.
Скачивание реализовано через точку расширения IBinaryFileDownloadHandler, компоненты которой возвращают нужный ActionResult. В системе в данный момент определены следующие обработчики скачивания:
- EmptyBinaryFileDownloadHandler- обработчик скачивания, вызываемый в случае, если BinaryFile не имеет контента.
- LocalBinaryFileFullDownloadHandler- обработчик скачивания файла целиком, вызываемый в случае, если у BinaryFile контент хранится на локальной файловой системе.
- LocalBinaryFilePartialDownloadHandler- обработчик скачивания части файла, вызываемый в случае, если у BinaryFile контент хранится на локальной файловой системе. Требуемый диапазон указывается в HTTP-заголовке "Range".
- DistributedBinaryFileDownloadHandler- обработчик скачивания, вызываемый в случае, если у BinaryFile контент хранится где-то в распределённом хранилище.