Микросервисы vs. Монолит

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
В начале ноября на ютуб-канале Яндекс.Практикума прошли дебаты «Микросервисы, Монолит и Зомби». Ведущие дебатов — наставник курса «Мидл Python-разработчик» Руслан Юлдашев и техлид курса Савва Демиденко — разобрали архитектуры двух систем, прошлись по реальным задачам и ошибкам из своей рабочей практики и по очереди защищали свои позиции.

Обсуждение растянулось на 100 минут, поэтому мы публикуем сокращённую текстовую расшифровку.



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

Вы узнаете, как врачи регионов России не получали зарплату из-за микросервисов и сколько монолитов можно запустить, пока согласовывается интерфейс между сервисами.

Определение микросервиса и монолита


Понятно, что ставить вопрос, что лучше — микросервисы или монолит, не совсем уместно. Такой уровень архитектуры зависит от кучи факторов, и мы попробуем разложить некоторые из них.

Савва Демиденко — выступает за микросервисы


Занимаюсь разработкой в Avito, делаю программу курса «Мидл Python-разработчик» в Яндекс.Практикуме. Закончил Бауманку и Технопарк@mail.ru. Разрабатываю на Python и Golang. Люблю решать архитектурные задачи в веб-программировании.

Исходя из моей практики, монолитом называют то приложение, которое во время разработки сложно запустить на локальной машине.

Пример из последнего, с чем я сталкивался: в компании Avito есть легаси на PHP и продуктовые задачи, которые нужно решать этим кодом, чтобы двигать бизнес и зарабатывать больше денег. Но эта кодовая база старая — возможно, ей больше 5 лет. Её сложно поддерживать. Чтобы сделать продуктовую фичу, нужно много времени. Поэтому в Avito мы начали распиливать монолит в сторону микросервисов.

Микросервисом можно назвать тот продукт, который вмещается в голову одного разработчика. Микросервис — это фреймворк, хранилище, кэширующая система и 3—5 эндпойнтов.

В других компаниях я сталкивался с более широким понятием микросервиса. Например, сервис авторизации, у которого явно больше 3—5 API-интерфейсов: он регистрирует юзера, выдаёт токены JWT и реализует механизм одноразовых ключей. У него большая кодовая база, но он всё равно считается микросервисом.



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

Итак, микросервис — это небольшой сервис, который использует одно хранилище, один key–value storage для кэша и не больше 10 эндпойнтов. Это удобно тем, что на знакомство с продуктом уходит пара дней: тебе дают задачу, ты изучаешь сервис и готов дальше с ним работать.

Руслан Юлдашев — выступает за монолит


Пишу код и запускаю веб-сервисы от Ташкента до Сан-Франциско. Наставник Яндекс.Практикума. Самоучка. Люблю, когда код облегчает жизни людей.

У меня задача попроще, потому что большинство проектов в мире — монолиты. Обычно это один репозиторий, где находится весь код проекта, включая пользователей, бизнес-логику, отправку нотификаций — всё, что делает приложение, хранится в одной большой кодовой базе.

Эти сервисы могут масштабироваться и работать под нагрузкой. Как правило, всё написано на одном языке. Возможно разделение языка для фронта и бэкэнда.

Если у вас нет message bus или queue и прочих характерных для микросервисов инструментов, то наверняка у вас монолит.

Общая архитектура


Постановка задачи


Мы будем говорить про различия в подходах на примере проекта онлайн-кинотеатра — аналога Netflix или Кинопоиска. Примерно такой проект делается на курсе «Мидл Python-разработчик» в Практикуме.



Весь курс строится следующим образом: после начала обучения студент проходит маленькое испытание, в котором решает задачи. У студента есть артефакт в виде SQLite, в котором заранее подготовлено 999 фильмов. Студент пишет немного кода, чтобы создать pipeline перекачки данных из этого хранилища в Elasticsearch.

Попутно в курсе мы рассказываем, почему мы используем SQLite, зачем нам нужен Elasticsearch. Вокруг этого хранилища мы создаём обвязку в виде Flask и обстреливаем тестами end-to-end.

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

По условиям задачи мы ожидаем большую нагрузку, собираемся делать супервзрывной большой проект, будем двигать всех в Рунете — это стартап. У нас есть примерно полгода, чтобы его запустить. Для успеха нужно проектировать архитектуру и выбрать монолитом или на микросервисах.



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

Архитектура модулей


Вернёмся к архитектуре. У нас есть вот такая схема будущего онлайн-кинотеатра.



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

Пункты со второго по шестой — это доменные области. Похожим образом их можно выделить и в монолите.

Сразу рассмотрим третий блок — это будет gateway нашей системы. Все запросы от любых клиентов — телевизоров, планшетов — будут попадать сюда, в нашу точку входа. Её задача проста: она принимает и обрабатывает все запросы от юзеров, агрегирует и отдаёт данные.

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

Сервис UGC будет заниматься хранением пользовательских данных: комментарии, лайки, история просмотров. Мы предоставим студенту возможность самостоятельно выбрать хранилище, оцениваем классическое реляционное решение или рассматриваем то, что лучше решает задачу вертикального или горизонтального масштабирования. Здесь же мы говорим об event sourcing и задачах для него.

Сервис нотификации уведомляет пользователя о чём-то новом: вышел фильм из закладок, кто-то оставил комментарий и вообще любые новости. Это решается с помощью большого числа технологий: рассылки очередями данных, пуши на веб-устройства с помощью вебсокетов. Ещё в онлайн-курсе мы немного затрагиваем тему пушей в мобильных устройствах на iOS и Android.

Руслан: Если я буду делать кинотеатр через монолит, то модули мне понадобятся такие же, как и здесь: админка, аутентификация, нотификации. Однако, всё это будет в одном репозитории. Скажем, я буду использовать Django, который решает большую часть из указанных задач. Останется Elasticsearch, чтобы сделать умный поиск, но это будет не отдельный сервис — я буду слать запросы напрямую из Django.

Схема в чём-то останется такой же, но с точки зрения кодовой базы набор технологий будет меньше. Большинством задач будут заправлять Django и Elasticsearch.

Особенности для команды


Мы разобрались, какими могут оказаться микросервисная и монолитная архитектуры. Теперь посмотрим, на что нужно обратить внимание при реализации сервиса.



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

Савва: В микросервисах мы практически бесплатно получаем неплохую отказоустойчивость.

Микросервисы — не серебряная пуля. Они добавляют оверхеда и издержек на согласование контрактов между сервисом gateway и сервисом авторизации. Контракты — это различные схемы, например, JSON или спрятанная документация.

В случае с монолитом всё просто: мы делаем родительский класс, у которого есть функции и параметры, за которые отвечает сам язык. В случае с микросервисами всё более гибко и не так явно. По умолчанию мы не можем проверить, насколько согласованы контракты сервисов.

Руслан: Можно посмотреть примеры монолитов в больших компаниях. В чате пишут, что ivi сделан на Django — видимо, имеют в виду, что на монолитной архитектуре. Я знаю, что Instagram долгое время успешно работал на Django. Среди примеров также Basecamp и Shopify. Есть крупные монолитные приложения с крутой технической командой, которую невозможно обвинить в том, что они не умеют делать микросервисы. И они всё равно выбирают свой монолитный путь.

Наверняка будут такие же примеры с микросервисами.

Савва: Давай зацепимся за названное. У того же Instagram был суперпростой сервис: ты заходишь, заливаешь картинку, её лайкают. Причина выбора Django понятна — нужно было быстро проверить гипотезу, что люди будут этим пользоваться.

Возможно, основатели Instagram и сами в него не верили. Зачем он, когда есть Facebook и куча других сайтов, которые решают ту же самую задачу? Но Instagram заметили, люди стали заходить. В нём не важна инфраструктура, он не предлагает тебе очень классный алгоритм — он ценен из-за контента, который и приносит деньги.

Instagram просто заливает всё деньгами. Сотрудники оценили, сколько уйдёт на горизонтальное масштабирование, сколько — на переписывание. На данный момент им проще докупить тачек в Amazon и жить с этим.

С точки зрения сроков, монолит — самое классное решение. Если нужно опробовать продуктовую гипотезу — берите Django, DRF (Django REST framework) и пишите модели.



Когда приходят пользователи и деньги, можно подумать, распиливать ли монолит. Таких кейсов много: тот же Avito, почта в mail.ru, большое количество зарубежных историй, например, Booking.com.

Микросервисы помогут, когда уже есть сформировавшийся продукт с тенденцией к росту. Например, часть команды пишет на Java, но Java-разработчики дорогие, их сложно искать по рынку. А если есть несколько микросервисов на Java и несколько на Golang, то получится одновременно закрывать вопрос по найму и одной части команды, и другой.

Руслан: Согласен, это плюс, что получается сочетать языки. Я это использую в команде, где есть абсолютно монолитный проект, по большей части написанный на PHP. Когда мне понадобилось добавить нестандартную логику, написанную в мобильном приложении, то оказалось проще быстренько рядом поднять один сервис на Python, подключить Redis и дать все доступы нужным разработчикам.

Савва: Однако, когда ты просишь человека добавить фичу в большой монолит, он может испугаться. Он видит кодовую базу на 10 тысяч строк кода и думает, где начать — порог входа выше.

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

А еще если ты пишешь на микросервисах, проще искать проблемы. Они сразу локализуются: ты видишь, что у тебя выросло время ответа, и откатываешься на предыдущую версию.

Руслан: Здесь я не соглашусь. Это тот пункт, который в монолите часто намного безопасней: везде есть typechecking и одна структура данных, а не десять пользователей в десяти микросервисах, в каждом из которых по чуть-чуть данных. Вот он один, на него есть тесты и типизирование. Пройдя по стектрейсу, связанному коду и вызовам функции, ты соберёшь картинку того, с чем работаешь.

Но я знаю, что SOLID и Go структурируют разработку и добавляют дисциплины. В отличие от монолита микросервисы выстраивают правила взаимодействия с продуктом.

Савва: Да, в новом коллективе разработчиков мы часто сталкиваемся с тем, что у всех очень разный уровень. Кто-то знает паттерны, тот же SOLID, DRY, KISS, а кто-то впервые о них слышит.

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

Здесь можно провести параллель с языком Golang и Python. Python — сильный язык, в котором возможны классы и метаклассы, он тебя никак не сковывает. Уровень кода джуна, мидла и сеньора кардинально различаются. Golang проще в освоении, но накладывает ограничения. В этом случае и мидл, и сеньор пишут схожий код. Это одновременно и хорошо, и плохо. Язык пытается защитить тебя от ошибки. К примеру, Golang пропагандирует идею наличия интерфейсов, и для написания тестов нужно «мочить» эти интерфейсы — иначе качественных тестов не будет.

В этой аналогии монолиты чем-то похожи на Python, а микросервисы на Golang.

Особенности микросервисов


Рассмотрим проблемы микросервисов, которые затрудняют их использование.



Распределенные транзакции


Это задача, которую часто задают на собеседованиях. Она звучит примерно так: есть два дата-центра — один в Санкт-Петербурге, другой в Москве. Нужно сделать транзакцию из одного хранилища в другое.

Это типичный кейс, на котором объясняют транзакции. У Васи нужно снять деньги и перевести Пете.



Савва: В случае транзакционной модели реляционных баз данных всё просто: открываем транзакцию, списываем, добавляем, закрываем транзакцию. К сожалению, в микросервисных и распределённых системах задача не такая тривиальная. Пока мы списывали деньги у Васи и внезапно попросили добавить Пете, может что-то сломаться. То есть у Васи снимем, а Пете не добавим.

Поэтому мы используем очереди. Мы списываем у Васи деньги и кладём это сообщение в очередь. Затем мы ждём сообщения, что задача выполнена и можно идти дальше. После этого мы добавляем в очередь сообщение, что Пете деньги нужно добавить. Даже если задача падает, мы её повторяем. И так до тех пор, пока Петя не получит деньги, и задача не будет закрыта.

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

Руслан: Я бы воткнул в Django @transaction.atomic, и у меня всё бы заработало.

Савва: Да, это круто, но что ты будешь делать, когда твоё хранилище перестанет влезать на один сервер или в один дата-центр? Тогда ты захочешь распределённое хранилище. Хорошо, если Django умеет поддерживать транзакционный механизм с распределённым хранилищем. Если нет, начнутся проблемы.

Согласование контрактов и версионирование API


Руслан: Ещё одна из микросервисных проблем — согласование контрактов. Если мы делаем много сервисов, то у нас уже не поддерживается статическое типизирование языка, нет подсказок IDE о том, как вызывать методы, и вообще общих методов может не оказаться.

Ещё хуже — когда всё согласовали, всё работает, а потом в одном из сервисов сторонняя команда взяла и что-то изменила. И теперь нужно пройтись по всем сервисам и всё обновить.

Савва: Согласование контрактов — это и правда боль. Это оверхед на «сходить в команду» и выработать формат. Зато за 15 минут на встрече мы договариваемся, какие данные я буду отсылать и как ты на них ответишь — и уйдем писать код.

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

Ситуация со сменой имени API возможна где угодно, в том числе в монолите. Например, бывает, что монолит общается с интернетом и к нему получает доступ мобильное приложение. Задача решается версионированием API. При смене формата API версия повышается, например, из 2 в 3. При минорных изменениях, когда добавляется новое поле, ничего не сломается. Но нужно помнить об обратной совместимости.

Распределенные данные


Руслан: Что насчёт JOIN? Как это решается на уровне микросервисов, если мне нужно собрать данные из двух сервисов?

Савва: Как и в монолите: делаем несколько запросов в различные источники данных. С микросервисами нам помогает асинхронное программирование. Мы много говорим про это в нашем курсе, этому посвящён даже целый спринт. Мы сравниваем асинхронное программирование в разных языках — Kotlin, Golang, Python.

Роль JOIN будет выполнять gateway. Когда нода приходит с запросом, нам нужно сходить в сервис пользовательских данных, в сервис авторизации и аутентификации, подтянуть его комментарии, историю просмотров — и отдать. Вместо JOIN мы выполняем какое-то число HTTP-вызовов. Эти вызовы должны быть дешёвыми по ресурсам, для чего используется асинхронное программирование.

Аналитика и логи


Руслан: Хорошо, разобрались с данными, транзакциями и контрактами. Есть ещё одна проблема микросервисов, с которой я сталкивался в одном из проектов. Мы запускали онлайн-соревнование: студенты решали задачки. При этом для нас было важно собрать данные, откуда они приходят. У нас был модуль, который проверял по IP, из какой страны и региона приходит участник, а также сравнивал результаты — мы хотели узнать, где лучше знают математику. Мы написали микросервис, который собирал эти данные и генерировал отчёт. Но когда конкурс прошёл, в отчёте все студенты оказались из Москвы.

Так получилось, потому что данные пробрасывались от одного микросервиса к другому и на продакшн-реализации IP терялся. В тестовом окружении всё было окей. Мы потратили выходные, чтобы через AWK и grep связать логи Nginx одной машины с логами запросов от другой.

Савва: Такие проблемы на уровне микросервисов встречается часто. Обычно это задача решается через юнит, который настраивает ELK-схему для Docker. Скорее всего, если у тебя настроены микросервисы, то у тебя есть Kubernetes, а в нём — Docker, и они пишут под себя логи в формате JSON. Logstash собирает эти логи и перекладывает в Elasticsearch, к нему прикручивается GUI в виде Kibana. Все логи агрегируются в одно большое хранилище.

В монолите они бы лежали под ногами, а здесь с ними идет работа через красивый GUI. Из минусов — это дороже с точки зрения настройки: тебе нужен человек, который это сделает и будет поддерживать.

Руслан: А лучше пять человек. Потому что если у тебя Kubernetes, то меньше пяти девопсов один Kubernetes не настроят.

Савва: На самом деле это правда. Исходя из моего опыта, обычно одна команда занималась инфраструктурой. Её ресурса не хватало, появлялась ещё одна. Kubernetes тяжело готовить, но и объём сервисов был большой — порядка 300—400.

В монолите мы бы столкнулись с похожей ситуацией. Представь, что у тебя есть команда, которая делает один сервис — у неё будет девопс. И так будет с каждой командой, которая пишет свой маленький монолитик.

Руслан: Ещё один бедовый кейс из моей практики. В одном стартапе настраивали Kubernetes около года — у них был один девопс, который не так много с этим работал. И когда уже всё настроили, достигли автоматического горизонтального и вертикального масштабирования, кончились деньги и команда расформировалась.
Если у вас нет практики, опыта и денег, чтобы делать эту структуру, и вообще уверенности в достижении этой высокой нагрузки, то в микросервисы идти рановато.

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

Доступность приложения — Graceful degradation


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

Graceful degradation — это подход, когда мы понимаем, что сервис может не ответить или отдать плохие данные, и вместо этого мы отдаём заглушку. Пользователь получает статус анонимного пользователя, и онлайн-кинотеатр работает дальше.

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

Особенности монолитов


Перейдём к особенностям и проблемам монолитов.



Высокая связность


Савва: Высокая связность — это как раз то, с чем редко сталкиваются в микросервисной архитектуре. При проектировании микросервиса мы понимаем, что ему будет сложно взаимодействовать с другими компонентами системы.

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

Руслан: Если мы говорим про плюсы микросервисов, то многое из этого можно получить и в монолите. Если речь идёт про уменьшение связности, то модели из разных приложений имеют минимум связей друг с другом. И чаще всего это не перекрёстные связи, а в одну сторону.

Хороший маркер, что архитектура на уровне БД сделана хорошо — можно ли поднять его с нуля, то есть прогнать миграции и запустить проект. Часто бывает, что в каждом апликейшне по 60 миграций или больше. Если попытаться поднять проект с нуля миграций, он всё время будет валиться.

Также помогает локально отключить какой-нибудь из апликейшнов и посмотреть, как много проблем получится. Если всего в 2—3 местах появились заглушки, и всё работает, то скорее всего, модуль отделился успешно.

Сложно горизонтально масштабировать


Прежде, чем пытаться «переписать всё на микросервисы», стоит начать с шардинга и оптимизации кода.

В случае оптимизации с помощью трассировки запроса и профайлинга отслеживаются медленные участки. В большинстве проектов есть избыточные вызовы в циклах. У систем continuous integration есть плагины с анализаторами кода. Они обнаруживают фрагменты, которые можно вынести из цикла или исправить другие проблемы в кодовой базе.

Например, если где-то подтормаживает база, нужно проверить, нет ли запросов в цикле с помощью Django Debug Toolbar (если это Django) или другим способом. В случае PostgreSQL необходимо включить логирование медленных запросов дольше 0,1 секунды и скормить этот лог анализаторам. Если распарсить топ медленных запросов и топ самых частых запросов и починить эти случаи, то наверняка можно ускорить любую систему. Далеко не обязательно для этого переписывать всё с нуля.

Сложно тестировать


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

Есть один из вариантов решения, который мы использовали сами: распилить сервис на либы. Это тоже своего рода микросервисный подход. При этом мы не создаём оверхед на HTTP-запросы и не теряем связность компонентов. В нашем приложении мы можем устанавливать себе не только внешние, но и собственные либы от других отделов. Таким образом мы вынесем слой данных и будем параллельно разрабатывать каждый из них.

В чём-то это сильно повторяет подход микросервисов. Даже трудно сказать, микросервис это или монолит.

Обычно про микросервисы говорят, что это HTTP-запросы. При этом возможен параллельный изолированный репозиторий со своими тестами. Сохраняются все плюсы, которые есть в связном монолитном коде, работает подсветка в IDE, можно провалиться в код микросервиса другой либы и изучить его. Скорость разработки остается монолитной. При этом мы имеем те искусственные ограничения, которые нас дисциплинируют.

Единственное — нужно не забыть про версионирование. Это особенно важно, если использовать общие либы, допустим, слой данных Django-модели или Pydantic-модели. Хорошо решает проблему SemVer — соглашение о том, как версионировать библиотеки

Работать становится проще, когда мы придерживаемся SemVer-версионирования, в requirements.txt записаны не только внешние модули, но и модули команды проекта, и мы разделили части приложения на либы. Это создает проект низкой связности, который при этом имеет много преимуществ монолита.

Сложный найм


Савва: По поводу многоязыковости. Пусть мы пишем монолит на Java. Как ты считаешь, хорошо это или плохо?

Руслан: Не могу дать однозначного ответа. Я уже рассказывал про случай, когда PHP-разработчики были максимально заняты, а свободный питонист быстро поднял рядом микросервис. Это было классно. В то же время понятна проблема монотехнологичности. Если у меня проект на Java или каком-нибудь Rust, то искать новых специалистов тяжело.

Я однажды попробовал такой подход: в вакансии на поиск разработчика я описал задачу и дал 5—6 языков на выбор. Мне действительно было без разницы, пишет разработчик на PHP, Python или Node.JS. Задача привлекла внимание — она отличалась от других. Обычно ищут на определённый язык, а здесь пиши на чём хочешь.

Отозвалось много людей. Тут возник затык: я не знал, как их сравнивать. Сложно проводить собеседование, потому что все 5 языков я не знал, поэтому пришлось искать дополнительные ресурсы на аутсорсе или в другом отделе.

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

Савва: Пока ты говорил, я придумал ещё один плюс. Если у нас монокоманда с одним языком, то в идеальных условиях экспертиза самого опытного сотрудника будет растекаться ко всем остальным. Он будет тянуть всех вверх.

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

Руслан: Можно даже школу открывать — мы так и делали. Когда-то я тогда писал на PHP, и мне что-то нравилось, что-то нет. В то время одна компания в Казани организовывала бесплатные курсы по Python, и я вписался. После курсов они меня взяли. Внутри была построена хорошая вертикаль роста сотрудников.

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

Но это история десятилетней давности. Сейчас софт пишется по-другому. Это заметно даже на примере нашего курса в Практикуме: сегодня много задач решается через API, а не через код. Интернет работал медленнее, и ещё не было таких крутых инструментов, как Postman, GraphQL или JSON Schema.

Сейчас без этого вообще не живется. Появились мобильные приложения, которые ставят высокие требования к API, частично поменялась конъюнктура, писать сервисы стало проще. Мы не привязаны сильно к языку.

На нашем курсе мы качаем людей от джуниоров к мидлам и чуть-чуть выше. Но большая часть курса посвящена не особенностям Python, а особенностям Elastic, брокеру очередей, отказоустойчивости, логированию, Nginx. И это не наша выдумка — это требования тех вакансий, которые на рынке. Если посмотреть, кого сегодня хотят разные компании и как делаются сервисы, там действительно меньше требований к языку и больше к инфраструктуре и «насмотренности».

Сложность локального развертывания


Савва: Последний пункт в твоём списке меня пугает больше всего. Монолиты тяжело разворачивать и программировать локально. Чтобы поднять монолит, может не хватить 16 ГБ ОЗУ. Приходится искать хаки или что-то выключать.

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

Руслан: Здесь легко парировать. В некоторых микросервисных командах на то, чтобы заставить микросервис работать, может уйти неделя.

Часть сложностей локального развёртывания на монолите решаются с помощью упаковывания в Docker, часть — с помощью конфигурации. Некоторые компоненты делаются отключаемыми или в виде моков. В таком случае разворачивается с «живыми» данными только та часть приложения, с которой нужно работать.

Савва: Отвечая на твой вопрос о том, как запуститься, если микросервисов много. Это решается просто.

В компании наверняка есть staging. После запуска микросервис ходит в стейджинговое окружение и получает данные. Если не получается обнаружить проблему в рамках сервиса, и ты знаешь, что она в другом месте, ты берёшь эту часть системы и поднимаешь локально. У тебя два микросервиса — всё остальное в стейджинге. Это тоже заметно экономит время.

Вопросы зрителей


Были ли случаи, когда микросервис объединяли в монолит?

Руслан: У меня есть такая история.

Проект разрабатывали около года. Его специфика — софт для больниц и частных клиник, который считал, сколько должны заплатить страховые компании. Был большой объем данных, потому что в каждом регионе России в среднем около 4 миллионов людей обращаются в больницы. Каждое обращение учитывалось в этой системе. На выходе требовался отчет, сколько заплатить конкретной больнице.

Мы сразу решили делать микросервисы: модуль авторизации, модуль подсчёта, модуль приёма данных, модуль выгрузки данных и так далее. Была даже шина данных на Erlang.

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

Где-то автоматически происходил перерасчет, запускались новые таски через RabbitMQ Salary. На скриншоте видно количество необработанных задач, хотя они должны были проходить за долю секунды. Их там несколько миллионов.



Они копились, потому что считались неправильно. Что-то с этим сделать было тяжело. У меня тут показан один из серверов, который был под завязку загружен и не справлялся.

В этом хаосе мы пытались что-то править, получалось плохо. Мы поменяли структуру данных в одном сервисе, а в другом микросервисе этого не сделали. Или сделали, но там уже были запущенные процессы, которые не остановились, у нас это всё обрабатывается через message bus. Или где-то потерялись сообщения из очереди: то есть сервис нахватал задач из RabbitMQ и вместе с ними скончался. После этого задачи по одной из причин не вернулись обратно в очередь.

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

За три дня мы объединили все микросервисы в один здоровенный монолит, поставили нормальные транзакции с единой структурой данных. Сразу система не завелась, но в течение недели мы стабилизировали ситуацию. Всё стало работать нормально.

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

У нас много микросервисов. Как лучше всего реализовать логирование?

Руслан: Классика для этой задачи — ELK. Из каждого микросервиса мы кидаем запрос в Elasticsearch, собираем логи с помощью Logstash, а потом смотрим с помощью Kibana. Также здесь хорошо подходит Grafana. Единственное, что нужно помнить — нельзя терять пользователя. Чтобы отследить его путь, прибегают к fingerprint.

Можно ли выделить разбиение на микросервисы для фронта?

Руслан: Да, сейчас есть понятие «микрофронт». Здесь уже речь не про нагрузку, а распределённость и разный стек. Кажется, что и у этого есть свои преимущества, хотя в нашем курсе это не затронуто.

Выводы


Савва: Подведём итоги. Если нужно быстро протестировать гипотезу, вполне подходит монолит. Скорее всего, первые полгода единственное, во что вы будете упираться — это в базу: индексов не хватает, натюнено не так и прочие радости. Если база данных оптимизирована и «тормоза» остаются, если вы упираетесь в бизнес-логику, вам помогут микросервисы.



Руслан: В больших компаниях уровня Яндекса или Тинькофф требования могут быть известны сразу. В таких случаях можно сразу начинать с микросервиса. Если у вас молодая команда и стартап с неясными требованиями, рекомендую начать с монолита и дальше постепенно его распиливать.

Часто разработчики просто хотят что-то модное и клёвое, ещё не выжав всё из существующего монолита — тогда и начинается разговор про микросервисы. Но если определять профессионализм разработчика, само использование микросервиса — не показатель. Умение выжать максимум из того «железа» и софта, что у вас есть — вот показатель.

При достижении низкой связности, при оптимизации кода и при правильном использовании существующих инструментов, переход на микросервисы случится в некотором смысле автоматически, легко и естественно. Возможно, монолит не придётся распиливать — просто рядом будут появляться микросервисы.

Савва: Кстати, я не выбирал какую-то сторону для себя. Я стараюсь сохранять холодную голову и брать не ту технологию, которую хочу, а которая лучше подходит для продукта. Также я задумываюсь над тем, что вокруг компании, продукта и разработчиков.

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

Подведем итоги голосования: с большим отрывом победили микросервисы. Однако, на любой технический вопрос есть только один правильный ответ — it depends, то есть выбор зависит от кучи факторов.



Полезные материалы


  • Презентация
  • Репозиторий с огромным количеством полезных материалов по архитектуре

Доклады коллег об архитектуре:
  • Реализация микросервисной архитектуры (Авито)
  • Инструкция по отпилу, или как мы сервис сессий из монолита выносили (Авито)
  • Пятьдесят оттенков микросервисов (Dodo pizza)
  • Обратная сторона сервис-ориентированной архитектуры (Booking)
  • Боль микросервисной архитектуры (Fintier)

Статьи об упоминавшихся терминах:
  • SOLID
  • SemVer
  • Graceful Degradation
  • Inversion of Control (IoC) и его реализация на Python:
  • Dependency Injection и пример Dependency Injection в FastAPI
  • JSON Schema
  • gRPC

Литература:
  • Высоконагруженные приложения. Программирование, масштабирование, поддержка

Самое важное:
  • Курс «Мидл Python-разработчик» в Яндекс.Практикуме
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какая архитектура лучше подойдет для решения такой задачи?
66.67% Микросервисы 2
33.33% Монолит 1
Проголосовали 3 пользователя. Воздержались 0 пользователей.
Источник: https://habr.com/ru/company/yandex_praktikum/blog/570024/


Интересные статьи

Интересные статьи

В последние годы только ленивый не слышал о микросервисах. «Долой монолитную архитектуру, микросервисы — это будущее веб-разработки!» — об этом трубят буквально все компании, в технол...
Микросервис — это структурная единица, в которой все данные и функции, относящиеся к какой-нибудь одной конкретной бизнес-цели, объединены в один сервис.Что ж, это достат...
Всем привет. Когда я искал информацию о журналировании (аудите событий) в Bitrix, на Хабре не было ни чего, в остальном рунете кое что было, но кто же там найдёт? Для пополнения базы знаний...
В 1С-Битрикс: Управление сайтом (как и в Битрикс24) десятки, если не сотни настраиваемых типов данных (или сущностей): инфоблоки, пользователи, заказы, склады, форумы, блоги и т.д. Стр...
История сегодня пойдёт про автосервис в Москве и его продвижении в течении 8 месяцев. Первое знакомство было ещё пару лет назад при странных обстоятельствах. Пришёл автосервис за заявками,...