Привет, Хабр! Я Кирилл Рождественский, тим-лид в компании TCP-Soft. Возвращаюсь со второй частью серии статей про миграцию с монолита на микросервисы. Напомню, что мы готовим их совместно с коллегами из Mango Office, с которыми создаем общий продукт, поэтому статьи публикуются в их блоге.
Ссылку на первую часть, в которой мы разбирали понятия и определялись, когда на микросервисы переходить стоит, а когда нет, оставлю здесь. В этой статье мы разберем шаблоны перехода с монолитной на микросервисную архитектуру.
Итак, 7 миграционных паттернов.
Удавка
Данный паттерн подойдет, когда мы хотим поэтапно вынести часть функционала из монолита, упростив при этом возможность отката.
Допустим, у нас монолит с модулем, который имплементирует два метода.
Мы решаем вынести модуль в микросервис. Как это реализуем? Первым делом создаем точку входа (proxy) между монолитом и пользователем — это может быть как готовое решение, например всеми любимый nginx, так и что-то самописное. На данном этапе, прокси является просто прослойкой, не несущей в себе никакой логики. Иногда, роль прокси может выполнять сам микросервис, однако использовать данное решение не рекомендуется, так как микросервис будет нагружен логикой, не относящейся к бизнес-домену.
Далее мы начинаем разработку нашего нового микросервиса.
После того, как часть функционала готова и протестирована, мы можем ввести сервис в эксплуатацию и настроить прокси таким образом, чтобы пользовательские запросы к перенесенному функционалу уходили в микросервис, а все остальное шло по старому пути — в монолит.
Хорошей практикой является добавление некоего логического переключателя (где — неважно; это может быть конфиг, админка и т.д.) — так называемого feature toggle, с помощью которого мы сможем выбирать вариант реализации. Это полезно в первую очередь для быстрого отката к монолиту, если в микросервисе обнаружится критическая ошибка.
После того как функционал протестирован и прошел «испытание боем», из монолита его можно удалить.
Периодически встает вопрос о том, какими порциями производить переключение функционала. Обычно удобнее и безопаснее переключать весь имплементируемый микросервисом функционал целиком. Это связано с тем, что часто между разными методами одного бизнес-домена существуют тесные логические связи. И в случае, если данные по какой-то причине не сходятся, и при этом один метод реализован в монолите, а второй уже успел переехать, поиск источника проблемы может сильно усложниться.
Композиция UI
Паттерн, больше распространенный среди веб-разработчиков, но десктоп-программисты тоже могут его применять. Представим, у нас есть веб-интерфейс с функционалом, который можно логически разделить на модули, например, встраиваемые фреймы. Мы можем начать переход на микросервисную архитектуру с выноса в отдельный сервис какого-то одного модуля (например, наименее важного, либо реже всего просматриваемого).
Архитектура может быть такой: frontend — разделы на сайте «Новости» и «Рейтинг лучших игроков», backend — модули новостей и рейтинга. Мы встраиваем в нее менеджер контекста (proxy) и разрабатываем микросервис рейтинга. После этого proxy перенаправляет соответствующие запросы уже непосредственно в микросервис. Хорошей практикой также будет добавление feature toggle, с помощью которого можно переключиться обратно на имплементацию на стороне монолита. Очень напоминает «удавку». С той лишь разницей, что тут рендеринг интерфейса происходит на стороне нового микросервиса.
Разделение по абстракции
Шаблон подойдет, если мы хотим вынести из монолита функционал, активно используемый разными модулями. Например, есть монолитный backend с модулями сделок и обращений, оба используют модуль email-уведомлений, а мы хотим обособить уведомления в отдельном микросервисе. Для этого реализуем интерфейс отправки email, с которым начинают взаимодействовать внешние модули. Внедряем на стороне монолита шлюз для взаимодействия с микросервисом. Шлюз реализует новый интерфейс, но параллельно с этим мы продолжаем использовать и старую имплементацию. На уровне конфига добавляем тогл, который переключает реализацию со старой-доброй монолитной на новую микросервисную. Переключаем, тестируем, и, если что-то не нравится, возвращаем обратно. Если же все устраивает, старую имплементацию удаляем, оставляя только шлюз.
Шпион
Паттерн для тех случаев, когда мы только разрабатываем функционал, и микросервис еще не до конца реализован. Либо если нам надо встроиться между монолитом и микросервисом для выполнения, например, логирования.
Шлюз перенаправляет запросы в заглушку с тем же интерфейсом, что и у конечного микросервиса. Она по сути не делает ничего полезного, а, например, пишет что-то в лог. Зачастую в данном шаблоне происходит параллельная имплементация (например, модуль email-уведомлений и шлюз в монолите работают параллельно).
Параллельное выполнение
Допустим, у нас есть алгоритмически сложный функционал. Мы хотим его внедрить, но при этом избежать большого количества ошибок. Для этого нам нужно апробировать новый функционал прежде, чем на него переключаться.
Первым делом мы создаем прокси-прослойку между пользователем и существующим функционалом (так же, как мы делали в случае с «удавкой»). Далее разрабатываем новый микросервис, тестируя и разворачивая его в боевой среде. Когда пользователь делает запрос (например, на формирование отчета), на стороне точки входа запрос дублируется и отправляется одновременно и в новую, и в старую реализации. Пользователю же возвращается только один ответ — тот, который вернул старый работающий сервис (никаких неожиданностей не будет). Результаты сравниваются, итог записывается в лог. Остается только наблюдать за логом, детектировать расхождения в отчетах, и фиксить баги. Если через пару недель / месяц приходим к выводу, что в последнее время в лог не писалось ничего, значит, можно переключаться на новую имплементацию.
Паттерн, описанный выше, достаточно простой, но имеет несколько весьма очевидных подводных камней. Во-первых, дополнительная логика нагружает точку входа, что может негативно сказаться на производительности. Во-вторых, если мы будем осуществлять запросы синхронно и последовательно, то увеличится общее время получения ответа клиентом (спросили старый сервис, подождали, потом спросили новый, опять подождали).
На этом список проблем не заканчивается, но все они решаемы. Вот некоторые способы того, что можно делать для их устранения:
Записывать исходный запрос в некий лог (или в очередь в брокере сообщений), после чего идти по старому пути. Некий сторонний сервис будет вычитывать лог (очередь) с определенной периодичностью, вызывать соответствующие методы и анализировать результаты.
Асинхронно выполнять два запроса, не дожидаясь ответа от нового сервиса.
На самом деле вариантов решений больше, и все они зависят от конкретной ситуации. Я просто хочу подчеркнуть, что данный шаблон столь же опасен, сколь и полезен. Работу по нему можно сравнить с неумелым обращением с оружием: одно неверное движение, и ты легко прострелишь сам себе конечность. Будьте осторожны.
Канареечный релиз
Шаблон подразумевает выпуск функционала только для части клиентов с целью снизить уровень негатива в случае ошибки. Если мы деплоим новый функционал, работающий с новым микросервисом, только для части клиентов, то в случае поломки пострадают не все.
Балансировать можно по-разному: выбирать случайных клиентов, явно указывать наиболее лояльных, выделять группы по географическим регионам. В любом случае подходить к выбору тех, кого переключать в первую очередь, необходимо с умом. Например, если взять 10% клиентов случайным образом, вполне может оказаться, что в эту долю попадут все випы, генерирующие 99% нагрузки. Тогда релиз по сути получится общим, и ни о каком снижении уровня недовольства можно не говорить.
Кроме того, переключать клиентов надо постепенно: 10%, 20%, 40% и т.д. Часто бывает так, что проблемы не проявляются при низкой нагрузке на сервис, а при переходе через некий критический порог, например, в 42% все взрывается.
Декоратор
Не совсем миграционный паттерн, больше относится к общим шаблонам, но при переходе на микросервисы может быть полезен. Например, когда мы перед имплементацией микросервиса хотим собрать данные по интенсивности использования функционала или специфические запросы, которые можно будет применять в тестах.
Тут все просто. Как обычно, мы реализуем некую точку входа, но в данном случаем мы еще и обогащаем ее необходимой логикой. Например, анализируем частотность запросов или считаем любые другие интересные нам метрики.
На этом закончим вторую часть нашей серии статей о переходе с монолита на микросервисы. Задавайте свои вопросы и делитесь мыслями в комментариях. Мой ник, под которым я отвечаю в комментариях: @vi_ki_ng. В следующей части разберем варианты работы с данными и вопрос их синхронизации.
Подписывайтесь на наши соцсети:
Аккаунты Mango Office
ВКонтакте: https://vk.com/mangotelecom
Телеграм: https://t.me/mango_office
Аккаунты TCP-Soft
Instagram*: https://www.instagram.com/tcp_soft
Facebook*: https://www.facebook.com/tcpsoftminsk
* Продукт компании Meta, признанной в РФ экстремистской организацией
Комментарии (2)
predbannikov_yurij
08.10.2022 16:51Почему мы так зависимы от рейтинга? Просто делайте как считаете нужным, а люди сами сделают вывод.
Georgii_Galechyan
Я немного в замешательстве... рейтинг статьи ушел в минус, но 9 человек добавили ее в закладки.