
Техника
Проектный руководитель
В практике ScienceSoft мы часто прибегаем к микросервисам при разработке больших и сложных веб-приложений, особенно облачных. Тем не менее, в начале сотрудничества некоторые наши заказчики задаются вопросом: «Почему мы должны отказываться от старого и проверенного монолита в пользу более затратной, трудоемкой разработки?» И они правы. Разработка на основе микросервисов гораздо более требовательна и ресурсоемка, но с учетом преимуществ, которые может получить бизнес, она может полностью окупиться.
Позвольте мне быстро напомнить вам суть обоих архитектурных стилей. Монолитная архитектура делает все это простым. Приложение имеет только один сервер и одну базу данных. Программа последовательно шаг за шагом реализует всю бизнес-логику, переходя к следующему этапу только после завершения предыдущего. Все соединения между модулями являются вызовами внутреннего кода.
Архитектура микросервисов — это частный случай сервис-ориентированной архитектуры (SOA). SOA — это стиль разработки программного обеспечения, в котором независимо развертываемые модули взаимодействуют друг с другом через протокол связи по сети. Что отличает микросервисы, так это степень, в которой эти модули связаны. Микросервисы более независимы и имеют как можно меньше общих элементов. Каждый сервер является атомарным по своей природе и выполняет одну определенную бизнес-функцию
Проще говоря, одному из наших клиентов понадобилось специальное мобильное приложение для розничной торговли. Приложение должно было войти пользователя в свой профиль, принять его заказ и отправить уведомления по электронной почте (подтверждение заказа, обновления доставки и т. д.). Инновационная и очень многообещающая стартап-инициатива уже получила очень положительные отзывы от бизнес-сообщества. После выпуска первых прототипов несколько крупных компаний захотели внедрить приложение в свои рабочие процессы. Для нас это означало, что мы должны были создать приложение, полностью оборудованное для бесперебойной работы в условиях высоких нагрузок и интеграций с различными внутренними системами бизнес-пользователей.
Во-первых, я предлагаю посмотреть, как обстоят дела с традиционно используемыми монолитами.
Когда пользователь открывает приложение для размещения заказа, система последовательно проверяет безопасность, регистрирует пользователя, обрабатывает его запрос, отправляет подтверждение заказа по электронной почте — проверяет успешное завершение сеанса. Что случилось? Все в порядке. Вырванное из контекста приложение просто работает так, как ожидалось, и выполняет свои основные функции. В идеальной ситуации. Однако вот что могло пойти не так на самом деле:
В монолите, если одна часть бизнес-логики приложения не работает или перегружается, все приложение может остановиться, так как оно не может перейти к следующему этапу работы. Вернемся к нашему примеру, представьте, что по какой-то причине уведомления не могут быть отправлены сразу. Пользователи не смогут успешно отправить свои заказы, пока эта часть бизнес-логики снова не станет доступной. В результате клиентский опыт может сильно пострадать.
Представьте, что нам нужно обновить монолитное приложение (внедрить новые технологии, добавить новые функции). Даже в случае незначительных изменений нам нужно было бы переписать почти все, а затем остановить старую версию на некоторое время (что означает потерю клиентов и заказов), чтобы заменить ее новой. Кроме того, мы должны были бы быть очень осторожны и избирательны с новыми внесенными изменениями, потому что они могут повредить всю программу.
Поскольку наш монолит продолжает развиваться и расти, он сталкивается с все большей и большей нагрузкой. В монолите производительность практически не масштабируется. Плохая работа — одна из главных причин потери лояльности клиентов. Они не будут ждать и могут просто купить у конкурентов. К счастью, у этой проблемы есть решение, и вот что мы делаем. Теоретически, чтобы справиться с большой нагрузкой, мы могли бы продублировать существующую бизнес-логику. Затем мы получим два одинаковых сервера и распределим нагрузку между ними с помощью динамического балансировщика, который случайным образом перенаправит запрос на менее загруженный из них. Это означает, что если изначально один сервер обрабатывал, скажем, 200 000 QPS (запросов в секунду), что делало его слишком медленным, то теперь каждый из них обрабатывает по 100 000 QPS, не испытывая перегрузок. Однако обслуживание многих серверов довольно затратно, и дальнейшее масштабирование находится под вопросом..
Теперь давайте посмотрим, что из себя представляет приложение, спроектированное как набор из нескольких небольших и независимых частей:
Один сервер, работающий медленно из-за перегрузки или даже полный сбой, не означает конец света. Зачастую пользователь даже не заметит никаких торможений. Когда профиль пользователя или сервер заказов недоступен, система просто перенаправит запросы на его заменители (поскольку у нас есть два сервера профилей пользователей и три сервера заказов). При сбое сервера уведомлений система продолжит свою работу и возобновит недоступную функцию, как только сервер будет восстановлен. Да, клиент получит уведомление не сразу, но, по крайней мере, его заказ не будет отклонен или потерян.
Когда сервисы полностью независимы, мы можем просто переписать нужные серверы, чтобы добавить некоторые новые функции (механизм рекомендаций, обнаружение мошенничества и т. д.). В нашем примере, когда нам нужно внедрить IP-трекер и сообщать о подозрительном поведении (как это делает Gmail), мы просто создадим сервер обнаружения мошенничества и немного изменим серверы профилей пользователей, а остальные серверы останутся в безопасности.
Слабосвязанный характер архитектуры микросервисов и ее невероятный потенциал масштабирования позволяют справляться с инцидентами с минимальным негативным влиянием на взаимодействие с пользователем. Например, когда мы видим, что некоторые из наших основных функций работают медленно, мы можем увеличить количество серверов, обрабатывающих необходимые функции (как мы начали с профилей пользователей и серверов заказов). В качестве альтернативы мы могли бы позволить им работать немного медленнее на некоторое время, если функции не являются жизненно важными (как мы сделали с уведомлениями). В некоторых случаях есть смысл вообще отключить некоторые функции. Например, в часы пик у нас могут быть всплывающие окна, которые показывают только текстовые описания без включенного изображения. Это потребует меньших мощностей, и положительный пользовательский опыт будет на месте.
Мы разбили приложение на микросервисы и получили набор полностью независимых модулей для развертывания и обслуживания. Как уже было сказано, в нашем приложении 2 сервера профилей пользователей, 3 сервера заказов и сервер уведомлений выполняли соответствующие бизнес-функции.
Однако разделение было лишь отправной точкой построения архитектуры, ориентированной на микросервисы. Чтобы сделать нашу систему успешной, было важнее и еще сложнее обеспечить бесперебойную связь между вновь созданными распределенными компонентами. Наши микросервисы обменивались данными либо через синхронизирующие протоколы HTTP/REST, либо через асинхронные протоколы AMQP, в зависимости от необходимости (при необходимости). Если вам нужно быстро узнать разницу между синхронными и асинхронными вызовами, я рекомендую проверить это объяснение — оно очень популярно среди наших клиентов и читателей блога.
Также пришлось ввести несколько промежуточных компонентов.
Мы внедрили шлюз. Шлюз стал точкой входа для всех запросов клиентов. Он позаботился об аутентификации, проверке безопасности, дальнейшей маршрутизации запроса на соответствующий сервер, а также об изменении или отклонении запроса. Он также получал ответы от серверов и возвращал их клиенту. Служба шлюза освободила клиентскую сторону от хранения адресов всех серверов и сделала их независимо развертываемыми и масштабируемыми. Как вы помните, обе функции были для нас крайне важны. Для нашего шлюза мы выбрали новый фреймворк Zuul 2. Поскольку мы хотели добиться максимальной масштабируемости производительности, было важно использовать преимущества неблокирующих HTTP-вызовов. И это именно то, что подарил нам Zuul 2.
Сервер Eureka работал как наше открытие сервера. Так как у нас было несколько серверов на одну функцию (и их количество действительно вскоре увеличилось), необходимо было обнаружение серверов, чтобы вести список используемых профилей пользователей и серверов заказов и помогать им обнаруживать друг друга.
Мы также создали резервную копию нашей архитектуры с помощью балансировщика нагрузки Ribbon, чтобы обеспечить оптимальное использование масштабируемых серверов профилей пользователей.
Для обеспечения большей отказоустойчивости и быстродействия нашей системы, изолирующей точку доступа к упавшему серверу, мы использовали библиотеку Hystrix. Это предотвратило уход запросов в пустоту в случае падения сервера и дало время перегруженному восстановиться и возобновить работу.
Поскольку мы не хотели, чтобы наши пользователи ждали положительного ответа от сервера уведомлений, чтобы продолжить работу, нам нужно было, чтобы уведомления по электронной почте отправлялись независимо. Для этого мы использовали брокер сообщений (RabbitMQ) в качестве посредника между сервером уведомлений и остальными серверами, которые позволяют промежуточный асинхронный обмен сообщениями.
Как и любой другой архитектурный подход, микросервисы не совсем безупречны. «Разделение за разделение» может оказаться не только бесполезным, но и негативно сказаться на вашем приложении. Чтобы узнать о возможных рисках, рекомендуем ознакомиться с подробным описанием плюсов и минусов микросервисов, подготовленным моим коллегой Геннадием Закалуским.
Прежде чем отправиться в такое большое и долгое путешествие по внедрению микросервисов, я настоятельно рекомендую получить профессиональную консультацию от опытных архитекторов Java. Они оценят ваши конкретные потребности и ограничения и помогут выяснить, применима ли архитектура микросервисов в вашей ситуации.
Если они реализованы правильно и в хорошем месте, я считаю микросервисы огромным подспорьем, когда речь идет о создании сложных приложений, которые работают с огромными нагрузками и нуждаются в постоянном масштабировании. Моя команда в ScienceSoft и я будем рады выполнить технико-экономическое обоснование и помочь вам безопасно и эффективно внедрить архитектуру микросервисов. Если вы хотите начать с микросервисов или перейти на них с существующего устаревшего монолита, просто свяжитесь с нами.