Megogo превратился из стартапа в один из крупнейших видеосервисов в странах Восточной Европы и СНГ. За это время наши технологии и подходы претерпели множество изменений и улучшений. Быстрый рост непременно вызывает массу проблем, к которым молодые компании часто попросту не готовы. Выбранный язык программирования не слишком подходит для выполнения поставленных целей, фреймворки сложно масштабировать, узкие места в каналах CDN. Поэтому технологическая трансформация Megogo была неизбежной.
В этом материале я хотел бы рассказать о нашем опыте перехода от монолитной к микросервисной архитектуре продукта и о наших выборах, которые впоследствии сделали разработку и поддержку системы чуточку проще.
API 1.0
Все началось с монолита, а точнее — с веб-сайта megogo.net. В 2011 и 2012 он был основой сервиса, с него начиналась экспансия. Некоторое время сайт, написанный на PHP, справлялся с быстрым ростом количества пользователей сервиса и набора функциональных возможностей, которыми он обрастал. Необходимость трансформации стала очевидной во время расширения онлайн-кинотеатра на другие платформы — следующим на очереди было приложение для Smart TV. По сути, сайт кинотеатра выступал в качестве API для этого приложения, поэтому нагрузка на веб-версию повысилась. Скоро должны были появиться приложения для смартфонов и планшетов. Стало понятно, что систему необходимо модифицировать.
У нас были различные варианты выхода из сложившейся ситуации. Но наиболее удобной и прогрессивной нам казалась возможность отказа от монолита в пользу микросервисной архитектуры. Чтобы расставить все точки над «і»: мы не считаем, что разделение на множество небольших сервисов — единственно правильный подход. Постепенный переход на микросервисную архитектуру подкупал гибкостью в выборе решений и технологий, распределенностью команд, масштабируемостью и модульностью, благодаря которой можно собрать платформу «по кубикам», как домик из Lego.
Переход на микросервисы
Конечно же, на практике получалось все не так гладко. API 1.0, то есть сайт megogo.net, стал основой для выделения отдельных сервисов системы. Разработка нового, полноценного и чистого REST API велась на Java с мыслью об обратной совместимости с уже существующими сервисами CDN, Smart TV и сайтом. Это делалось для того, чтобы в процессе перехода на микросервисы существующие решения работали и были доступны пользователям Megogo. Так появился jAPI Megogo.
Он создавался не за один день. Да и после получения первой инкарнации jAPI до появления полноценной микросервисной архитектуры нужно было преодолеть несколько горных хребтов. Под раздачу попали сервисы оплаты и подписки пользователей, написанные на PHP. Первым отдельным компонентом платформы, написанным на Java, стал сервис электронной телепрограммы (EPG v1). После него появился сервис пользовательских подписок — он отвечает за активацию услуг для пользователей кинотеатра и предоставление информации об активных подписках. Так что параллельно был выделен еще один сервис — биллинг, то есть выполнение оплаты пользователями (при помощи сторонних сервисов) и получение информации о проведенной оплате.
Параллельно с этим jAPI усиленно развивался и расширялся, фактически с его помощью реализовывались core-функции платформы. Затем в момент необходимости мы выделяли из jAPI самостоятельные сервисы, когда понимали, что в виде отдельного компонента функциональность позволит реализовать более гибкий процесс внедрения новых фич. Обычно так происходит с небольшими функциями. Например, сервис Stream, который отвечает за передачу в плеер нужного контента (видео, аудио, субтитры), и Loyalty, который выполняет расчет и начисление баллов пользователям по программе лояльности, были выделены именно по такому принципу. По этой причине сервисы часто выделялись на основе бизнес-логики, которую они реализуют.
Второй наиболее популярный у нас принцип разграничения на микросервисы — размер функциональности. Большой модуль, который изначально развивается самостоятельно и обособленно по сравнению с другими компонентами, сразу же выделяется в микросервис (даже если он не очень маленький). Так было с историей просмотров пользователей и электронной телепрограммой (EPG) для трансляций телеканалов. Кроме того, отдельные компоненты платформы появляются, когда разработка микросервиса оправдана с точки зрения используемого языка программирования или интерфейсов взаимодействия. Хороший пример — система рекомендаций, которая предлагает пользователям онлайн-кинотеатра персонализированный контент на основе различных данных об их поведении на платформе. Здесь полноценный Data Science, поэтому сервис написан на Python.
В итоге постепенно в процессе разделения монолита и расширения функциональности платформа Megogo разрослась до более чем 30 сервисов, в основном написанных на Java и Scala, а также Python.
Если собрать наш опыт перехода от монолита к микросервисной архитектуре, то получится несколько основных пунктов, которые одновременно включают как положительные, так и отрицательные моменты.
Распределенность работы
Очевидное преимущество микросервисной архитектуры — разработка отдельных функциональных компонентов платформы Megogo ведется параллельно. Несколько команд разработки создают на основе бизнес-логики различные блоки-сервисы, которые затем при помощи API подключаются в общую систему. Так что развитие системы и внедрение новых компонентов происходит быстрее, при условии, что API не сломан и новые сервисы независимы между собой.
С другой стороны, в нашем случае микросервисную архитектуру немного легче поддерживать — сервисы относительно небольшие (по количеству строчек кода). Каждый отдельный блок легче тестировать и поддерживать, в сравнении с монолитом.
Кроме того, у нас есть возможность экспериментировать с языками, фреймворками и технологиями. Правда, в качестве основы мы остановились на Java, Scala и Python, хотя также экспериментировали с PHP и Ruby on Rails, от которых впоследствии пришлось отказаться. Тем не менее, мы не ограничиваем себя. Если в какой-то момент придет понимание, что новый ЯП или технология лучше подходит для текущих задач, мы обязательно попробуем.
Отдельные сервисы для отдельных частей платформы
Положительная сторона микросервисов — возможность для каждого блока MSA реализовать строго определенную функциональность. Опять же, в этом случае упрощается поддержка и тестирование системы. Кроме того, учитывая особенности самого сервиса, намного легче для каждого из них подобрать наиболее оптимальное «железо», заточенное под выполнение определенных задач.
Распределенность системы и отказоустойчивость
Платформа Megogo технически разделена на два максимально непересекающихся периметра — CDN и сервисы. Для обеспечения и поддержания инфраструктуры команды NOC, системных администраторов и DevOps-инженеров также распределены для обеих частей платформы. CDN в данном случае — слишком громоздкая тема, заслуживающая отдельной статьи, поэтому сфокусируемся на сервисах.
Примечательно, что платформа не разделена на множество дата-центров. Каждый из них самостоятельный и содержит все данные платформы, что позволяет в каждом дата-центре обслуживать всех клиентов. Такая концепция предопределяет сложность задач масштабирования. Желательно увеличить производительность не одного дата-центра, а сразу всех ЦОД-ов с сервисами Megogo.
Важным слоем управления платформой является балансировка нагрузки. Основным инструментом для реализации гибкости стала быстрота и эффективность переключения трафика из одного дата-центра в другой с соблюдением хоть каких-то пропорций (желательно точных) при передаче по цепочке всех свойств сессий и данных о пользователях — cookie, IP, GEO и т.п. В нашем случае обязательна возможность горизонтального масштабирования, поэтому все сервисы, за исключением редких legacy-функций, горизонтально масштабируются и во многих случаях не хранят состояние (stateless). Некоторые из сервисов Megogo достигают 50 нод в пуле.
Естественно, в платформе одновременно используются реляционная и NoSQL базы данных, поэтому помимо эффективного общения нод также необходимо правильно обеспечить связь с БД, кластеризация которых очень важна. Такой подход предопределил для нас выбор продуктов в FoS-мире. Чем лучше они поддаются кластеризации и горизонтальному масштабированию — тем лучше для нас. Что, правда, не защитило от использования CouchDB :)
Ко всему прочему, мы используем динамический каталог сервисов, связанность каталогов между дата-центрами и внутренние балансировщики нагрузки, которые используют этот каталог для соединения сервисов друг с другом.
Однако отказоустойчивость на программном/аппликационном уровне — лишь половина головной боли. Так как физическая инфраструктура своя, то команде как минимум нужно продумывать, как аппликационная отказоустойчивость совместима с физической отказоустойчивостью. К примеру, бессмысленно масштабировать балансировщики нагрузки, если они размещены в одной стойке. Поэтому мы уделяем много времени анализу того, где у нас есть несоответствия между разными уровнями распределенности.
Reliability and performance-мониторинг
Для OSCentric мониторинга мы не нашли ничего лучше, чем Zabbix, в то время как мониторинг производительности осуществляют Grafana, Prometheus и Elastic. Опыт показывает, что контроль производительности необходимо выполнять не конкретным пользователям, а по всем системам. Поэтому мы стараемся вовлечь в создание в мониторинг-дашбордов как можно больше членов команд.
Какое-то время у нас получалось обслуживать платформу без поддержки второго уровня. Удивительно, но подход работал, даже когда сервис разросся. Однако сейчас техническая поддержка стала неотъемлемой частью наших agile-практик.
Технические тренды, на которые мы ориентируемся
Мы абсолютно точно двигаемся в сторону гибридного облака, которое в течение ближайших пары лет будет must-have. Конечно, операционная парадигма Infrastructure as code давно упрощает жизнь команд, но переход к гибридному облаку был бы сейчас достаточно серьезным испытанием. Кроме того, Megogo все больше основывается на распределенных вычислениях, за ними будущее.
Вместо выводов хочется сказать, что наш опыт перехода от монолита к MSA был скорее положительным, но это не значит, что теперь нужно сломя голову бежать лепить микросервисы. Возможно, именно для вашей системы такое решение будет бессмысленным и слишком затратным.