Эволюция архитектуры проекта. Из монолита в микросервисы

Здравствуйте, меня зовут Денис, и сейчас я работаю на позиции Team Lead в компании Fiverr. Уже 10 лет я занимаюсь разработкой веб-сервисов. За это время я участвовал в техническом развитии нескольких крупных компаний, таких как криптобиржа EXMO, автоматизировал работу складов компании Westwing и так далее.

У каждой компании своя история масштабирования и свой период времени для этого. Чем быстрее развиваемся, тем сложнее перестроиться из-за нехватки времени. Для начала нужно понять, каким образом вести масштабирование. На примере компании Fiverr я бы хотел разобрать эти шаги для бэкенд-части. Статья может быть полезной бэкенд-разработчикам, которые планируют расширять свой проект или интересуются, как работают компании с большим количеством трафика.

Шаг 1. Монолит. Кеширование

Достаточно одного приложения, которое обслуживает всех пользователей. Это отличный вариант, пока юзеров немного. Если какой-то запрос долго обрабатывается, стоит закешировать его участки. Конечно, если другого рода оптимизации не возможны.

Но при росте у монолита много недостатков. Например, логики приложения постепенно становится все больше, тесты начинают идти вечность. В какой-то момент разработчики меняют общую часть кода и должны поддержать это изменение еще в 20 местах, а тесты больше не проходят, и их тоже нужно чинить.

Хорошо, кстати, если тесты кто-то вообще писал. В этот момент далеко не все проджект-менеджеры понимают, почему такая простая задача занимает столько времени. Или в приложении начинает тормозить один участок, и приходится выливать несколько инстансов, чтобы распределить нагрузку именно на него, хотя для всего проекта это не нужно.

Как было в Fiverr

Мы начали создание сайта как монолитного приложения Ruby on Rails, поддерживаемого базой данных MySQL.

По мере того как сайт набирал популярность, а трафик увеличивался, необходимо было добавлять дополнительный слой кеширования в виде Memcached и Rails Action кэширования. Чтобы избежать вычислений данных для поиска и просмотра каталогов в реальном времени, были добавлены некоторые задания cron, в которых для разогрева кеша попадала заранее подготовленная информация.

Ранняя архитектура, состоящая из Rails, MySQL и Memcached

Но рост и масштабирование имеет свою стоимость, и в течение следующих 3 лет увеличение трафика заставило акцентироваться на оригинальной архитектуре.

Монолитная природа RoR означала, что любая ошибка или авария приведет к падению сайта, в то время как наличие единой базы данных в основном подразумевало, что с течением времени и по мере роста данных и объединения таблиц становилась все более и более продолжительной сборка для все большего количества запросов, и система стала работать медленнее с течением времени, заставляя каждого пользователя синхронно ждать получения, обновления и загрузки страниц. Беспорядок.

Добавим к этому растущую инженерную организацию с постоянно увеличивающимся количеством команд, и вы получите очень сложную в обслуживании кодовую базу.

Шаг 2. Сделать из монолита микросервисы

Если же не разделять монолит, то хотя бы новый функционал нужно писать в микросервисе. Один из исходов событий, который мне нравиться больше всего, это когда разработчик говорит проджекту: «Давай я сделаю эту задачу в отдельном проекте, и это будет раза в 2 быстрее». Конечно, он согласится, а разработчик заодно попробует еще новую технологию или хотя бы будет работать в чистом проекте без легаси-кода. По ощущениям это как пленочку с телефона снять.

Также микросервисы становиться намного легче масштабировать горизонтально.

Шаг 3. Выделить отдельные задачи, которые можно передать в асинхронную обработку

Если пользователю не нужно показать какие-то данные сразу после ответа сервера, то эту задачу в большинстве случаев можно поставить в очередь и сделать потом. Отправка email, создание отчетов и, возможно, даже обработка всего респонса целиком.

Fiverr в свое время объединил шаги № 2 и № 3.

Наряду с большой шумихой вокруг микросервисов мы начали детские шаги в путешествии, чтобы сломать монолит.

При разработке первого микросервиса были важные понятия, которые мы хотели продвинуть:

  • Простота начальной разработки — наша команда инженеров на бэкенде говорила на Ruby, и мы полюбили его за простоту и удобство для разработчиков. Мы хотели сохранить Ruby в качестве нашего языка (который позже эволюционировал в добавление Golang в наш стек, но об этом чуть позже).
  • Вирусное решение — усилия по масштабированию начинаются в первую очередь с инженеров, и мы должны были убедиться, что наша команда создает решение, которое является одновременно и глубоко стабильным, надежным, масштабируемым, и вирусным — это означает, что его принятие среди инженеров будет простым.
  • Асинхронный обмен сообщениями между сервисами — по мере увеличения трафика возникла необходимость в быстром и отзывчивом сайте, и мы поняли, что, позволяя пользователю ждать синхронного завершения обновлений, замедляли не только его сессии, но и всю платформу. Так как меньше запросов может быть обработано в то время, как другие пользователи используют сессию.

Шаг 4. Добавить события

Когда сервисов становится много и им нужно оперировать общими данными, то лучше всего передавать в очереди сами события о том, что произошло в системе, а микросервис уже сам решит, что ему делать с этими данными. Заодно в случае ошибки можно повторно отправлять событие на обработку.

Как это было в Fiverr

Мы создали «химеру», вдохновленную мифологическим двуглавым зверем, которая представляет собой Ruby-шаблон микросервиса, сочетающий в себе:

  • READ SIDE Grape API — для получения данных из ограниченного контекста, представленного сервисом, если это информация о пользователях, ценах на билеты или аналитика конкретного заказа.
  • WRITE SIDE RabbitMQ consumer — прослушивание темы сообщения и асинхронное выполнение обновлений модели ограниченного контекста сервиса.

«Химера» также пользуется:

Shared Core — так как обе «головы» находятся в одном репозитории, они наслаждаются повторным использованием кода и делятся бизнес-логикой домена, утилитами и так далее.

Общий набор коннекторов БД — переход на микросервисы инициировал использование специфических для сервиса баз данных, таких как MongoDB или Redis, которые мы сейчас широко используем. Сохраняя источник истины в виде реляционного кластера MySQL, мы хотели продвигать каждый сервис для доступа к собственной базе данных, оптимизированной для домена, и нам нужны были утилиты для упрощения подключения ко всем этим различным хранилищам.

Типичный асинхронный запрос на обновление будет выглядеть так, как при использовании «химеры»:

  1. Пользователь выполнит POST для создания заказа.
  2. REST API будет проверять запрос во время сеансов, пока ничего не сохраняя в базе данных. Это выполняется очень быстро, так как валидация поверхностная и включает в себя базовые входные валидации, а также некоторые проверки целостности данных с БД, которые выполняются без какой-либо тяжелой обработки.
  3. В этот момент, если валидация пройдена, в почтовый брокер (RabbitMQ) посылается сообщение с ключом маршрутизации, указывающим на собственного работника «химеры». Сообщение содержит всю информацию, переданную методом POST, и будет выполняться асинхронно, а пользователю не нужно будет ждать.
  4. Затем пользователю возвращается быстрый 201 статус, и он может сразу же продолжить использование платформы.
  5. Сообщение получает потребитель. RabbitMQ (работник) «химеры» имеет доступ к той же модели домена и может использовать его для выполнения команды и сохранения порядка в БД.

После представления «химеры», которая является разновидностью микросервиса, наша обновленная архитектура бэкенда выглядит примерно так:

В действительности за последние два года мы породили более 100 «химер», каждая из которых принадлежала и поддерживалась разными командами, создавая лучшую развязку и автономию в инженерном отделе.

Как показано на этой иллюстрации, новая шина обмена сообщениями использовалась для двух типов событий:

  • Командные события — события, посылаемые «химерой» внутрь своего работника (потребителя RabbitMQ) для асинхронной обработки обновлений.
  • Доменные события (Domain Events) — события, посылаемые химерой в подшаблоне pub любой другой химере, зарегистрированной для их прослушивания, информируя систему о том, что что-то случилось внутри ограниченного контекста «химерой».

Шаг 6. CQRS-подход и Event Sourcing

Теперь, когда в системе появились события, можно создавать отдельные хранилища со своей структурой. А если хранить историю всех событий, можно в любой момент построить наполненную базу данных или заново обработать ошибки. Или даже совершить откат базы данных до любого момента времени! Это технологически затратный процесс, но он дает неоспоримое преимущество в критических ситуациях, особенно в финансовом секторе.

С того момента, как мы начали активно использовать доменные события, у нас появилась возможность комбинировать их с применением шаблона CQRS — сегрегация ответственности командных запросов.

По сути, CQRS предназначен для создания оптимизированных для чтения представлений с целью повышения производительности приложений.

Рассмотрим один сценарий использования CQRS, который нам очень пригодился — аналитика! Простым примером для демонстрации будет приборная панель заказов продавцов:

Получение общего количества заказов на разных этапах потребует простой группы по команде в SQL. Но, когда дело доходит до больших масштабов, мы не хотим каждый раз при обновлении приборной панели «Управление продажами» выполнять такой объемный запрос для каждого из продавцов. CQRS на помощь!

Получение статистики заказа теперь уменьшено с o(n) до o(1). Прекрасно!

CQRS позволяет обновлять контекст ограниченных заказов, сохранять изменение статуса заказа в реляционной таблице MySQL, затем, используя доменное событие, информирующее любой заинтересованный компонент системы об этом изменении, мы фиксируем изменение в совершенно ином ограниченном контексте — аналитической «химере». И обновляем наш прочитанный оптимизированный MongoDB-документ, увеличивая конкретное значение bucket.

Эту модель мы продолжаем использовать для масштабирования и оптимизации производительности, которые мы проводим на протяжении всего пути в мире микроуслуг.

Итак, наша первая эволюция платформы в итоге выглядела так:

Вывод

Конечно, у такой архитектуры есть свои проблемы — это более сложная инфраструктура, более сложное взаимодействие сервисов, а распределенные транзакции — это еще то веселье.

Зато с таким подходом компания может быть готова к рекламе на Super Bowl, внезапному росту биткоина до 60К, порождающему массовые торги, увеличению количества товара на складе в несколько раз за ночь, огромному баннеру на главной странице App Store. А это все реальные истории. Когда выпадает шанс вырасти в несколько раз, нужно быть готовым, чтобы продукт это выдержал.

Похожие статьи:
Компания Kingston представила защищённый флеш-накопитель DataTraveler 2000, где применяется аппаратное шифрование данных и PIN-код для доступа к...
[Об авторе: Михаил Завилейский — Organisational Architect в DataArt. До прихода в компанию 10 лет работал программистом и менеджером] В статье...
Всем привет. Меня зовут Роман Базар. Я работаю в компании MGID на позиции Tech Lead, в основном занимаюсь web-разработкой. Не так давно...
[Материал опубликован в рамках конкурса статей на DOU] Technical writer. Он же documentation developer. Он же технический писатель. Он же инженер...
Мы собрали список компаний, которые готовы бесплатно принимать у себя в офисе разные IТ-мероприятия — выступления...
Яндекс.Метрика