Монолитные приложения — отличное решение, когда нужно запуститься «здесь и сейчас». А вот потом, когда появляется задача масштабироваться, гибкости монолиту уже не хватает. Так что «рукастые» инженеры создают микросервисную архитектуру.
Меня зовут Василий Клюев, я — директор по разработке в облачном хранилище Platformcraft, и в этой статье расскажу, как мы построили надежную систему объектного хранения с различными инструментами для работы с медиаконтентом, а также почему мы выбрали Rust и Go.
От монолита к микросервисам: зачем вообще переходить?
Сначала чуточку о боли.
Мы создали собственное объектное хранилище в 2012 году для хранения большого объема медиафайлов. Распределенный монолит позволил нам быстро развернуть сервис и закрыть потребности небольшого количества заказчиков.
Система состояла из различных сервисов, каждый из которых выполнял определенную функцию, а связывала их единая база данных. То есть была апишка (собственный API), был storage (хранилище), два бэкэнда (сам веб-сервис), транкодер и между ними шарилась БД.
Любые изменения в базе данных вызывали эффект домино: абсолютно каждую службу приходилось перезапускать и, самое жесткое, что в системе был транкодер. Куча людей отправляли видео на транскодирование, и если мы перезапускали службу, то все задачи пропадали.
Transcoder — сервис транскодирования.
Транскодирование — процесс преобразования аудио/видео потоков внутри медиафайла или потока прямого вещания. Потоки декодируются, параметры видео или аудио преобразуются, а затем потоки повторно кодируются.
Наличие нескольких центров обработки данных тоже усложняло ситуацию. В каждый дата-центр раскидывалось по копии. С ростом клиентов и сервисов, наша система становилось громоздкой, плохо управляемой и приводила к «узким местам» и проблемам с производительностью.
Такая модель была детищем хаоса, в простонародье называемым «распределенный монолит». Взаимосвязанные сервисы нужно было распутать и как можно скорее перейти на легкоуправляемые микросервисы.
И вот дальше расскажу, как мы осуществили переход, какие проблемы возникли при проектировании и какие языки программирования нам помогли в этом.
Проектирование микросервисов: как мы начали распиливание монолита?
Микросервисная архитектура — не универсальный ответ на все проблемы разработчиков. Нам требовалась гибкая, масштабируемая система, подходящая для хранения и обработки огромного количества медиафайлов. А для этого нужно было разбить монолитную структуру на более мелкие компоненты.
Мы хотели реорганизовать объектное хранилище, чтобы обеспечить удобство обслуживания и сохранить обратную совместимость. Каждый наш сервис должен был быть автономным, иметь прописанный функционал и свою БД.
Микросервисная структура идеально подходила по следующим причинам:
- Ее легче обслуживать, обновлять и отлаживать отдельные сервисы.
- Можно масштабировать отдельные компоненты приложения без привязки к ресурсам.
- Больше возможностей для разработки инструментов с использованием различных технологий.
- Можно работать одновременно над разными сервисами, что ускоряет разработку.
- Высокая отказоустойчивость системы из-за изоляции ее служб.
Итак, мы определили основные компоненты системы и начали процесс рефакторинга. Мы сохранили старый API для существующих клиентов, одновременно разрабатывая новые функции и сервисы, соответствующие микросервисной архитектуре.
Начали с разделения тесно связанных сервисов на независимые компоненты. Дальше внедрили новые сервисы с конкретными функциями. Например, создали службу «Garbage Collector» для удаления файлов и управления метаданными.
С самим переходом мы не торопились, а постепенно переносили функции из монолитной структуры в микросервисы, тестируя каждое изменение и сосредоточившись на минимизации взаимодействия между сервисами.
У нас появился новый дашборд, работающий с нашим новым API (Filespot), и мы перестали «ходить» в базу данных каждого сервиса. Теперь у всех сервисов появилась своя база, и при внесении изменений в один компонент, не нужно бегать и перезапускать каждый.
Таким образом все сервисы Platformcraft стали атомарными.
Создание экосистемы микросервисов для объектного хранилища
У нас специфический тип нагрузки из-за работы с файлами больших размеров. Поэтому нашей целью было разработать архитектуру, «переваривающую» большие медиафайлы, огромное количество метаданных и функций, сохраняя при этом высокую доступность и производительность.
Мы разделили обязанности между различными микросервисами (ведь шарить БД между сервисами — ну совсем дурной тон) и выделили:
- API Gateway — служба действует как точка входа для клиентов (набор апишек). Внутри API Gateway создана доменная модель, помогающая настроить взаимодействие между микросервисами.
- Garbage Collector — отдельно запускаемая служба для очистки хранилища (служба «ходит» по всем сервисам и удаляет объекты).
- Profile — сервис работы с профилями пользователей (email, телефон и пр.).
- Filespot — наша система хранения объектов, которая обеспечивает обработку контента, его хранение и извлечение.
- IAM — служба управления доступом к ресурсам, отслеживающая, есть ли права у клиента на доступ к конкретному объекту.
- API-шлюз — поддерживает единообразие интерфейса для клиентов.
- Transcoder — сервис транскодирования. Сервис транскодирования в Platformcraft работает без привязки к другим сервисам системы. Взаимодействие происходит посредством API и системы обмена сообщениями Kafka.
- Streamer — сервис потокового вещания, который позволяет создавать сетку вещания из заранее подготовленных медиафайлов, скомпонованных в блоки. Подготовка медиафайлов может быть осуществлена с помощью сервиса Transcoder с последующей загрузкой в Filespot.
- Recorder — сервис записи медиа потоков. Запись можно начать в любое время или задать по расписанию. Сервис принимает входной поток, формирует последовательность hls-чанков и предоставляет интерфейс для последующей выгрузки в Filespot.
По факту API Gateway ходит во все микросервисы, фиксируя API для клиента. Изменения во «внутренних» сервисах, скрытых за API Gateway, не приводят к изменениям текущих внешних интерфейсов, предоставляемых клиентам, что значительно увеличивает скорость и гибкость разработки.
Мы устранили необходимость использования базы данных между службами, уменьшив проблемы с синхронизацией и в итоге получили:
- Атомарный сервис с автономными базами данных.
- Минимизированное взаимодействие между сервисами.
- Уменьшение потенциальных точек сбоя.
- Упрощенное управление метаданными и объектами, включая загрузку, удаление и хранение файлов.
- Специально разработанный API для беспрепятственного доступа к возможностям нашего хранилища.
Какую роль в изменении архитектуры сыграли Rust и Go?
В начале нашего пути мы не могли себе позволить дорогостоящее одинаковое оборудование, поэтому нуждались в ПО, которое будет неприхотливо к железу. Требовался гибкий язык, способный обеспечить системе надежность и производительность.
Поэтому для создания хранилища данных я остановился на Rust. За счет низкоуровневого управления и высокой производительности Rust идеально подошел для разработки Storage. Система получилась устойчивой и удобной в обслуживании.
Для создания микросервисной архитектуры отлично подойдут:
- Java,
- Golang,
- Python,
- Ruby,
- Rust.
Основную часть сервисов мы пишем на Go, а самые критичные к производительности (хранилище, парсинг access логов и подобное) на Rust.
Golang мы выбрали потому, что он быстро осваивается и является компилируемым (нам не надо думать о ресурсах). Язык поддерживает разработку параллельных сервисов, которые потом можно будет легко связать друг с другом, и у него даже есть свой Garbage Collector.
Go подошел еще и потому, что имеет систему деления приложений на модули. Благодаря этому мы смогли создать сложные фреймворки и быстро развернуть наши микросервисы.
Выводы
Микросервисы предоставили нам гибкость, масштабируемость и производительность, необходимые для хранения больших данных и обработки медиаконтента. Если вы решили перестроить свою монолитную структуру, то помните о планировании, выборе инструментов и языке, а также наборе сильной команды.
Ну и надеюсь, что наш опыт вдохновил вас на собственные архитектурные преобразования.