- PVSM.RU - https://www.pvsm.ru -
Сегодня расскажем, как переводили на микросервисы монолитное решение. Через наше приложение круглосуточно проходит от 20 до 120 тысяч транзакций в сутки. Пользователи работают в 12 часовых поясах. В то же время функционал добавлялся много и часто, что довольно сложно делать на монолите. Вот почему системе требовались устойчивая работа в режиме 24/7, то есть HighLoad, High Availability и Fault Tolerance.
Мы развиваем этот продукт по модели MVP [1]. Архитектура менялась в несколько этапов вслед за требованиями бизнеса. Первоначально не было возможности сделать всё и сразу, потому что никто не знал, как должно выглядеть решение. Мы двигались по модели Agile, итерациями добавляя и расширяя функциональность.
Первоначально архитектура выглядела следующим образом: у нас был MySql c единственным war, Tomcat и Nginx для проксирования запросов от пользователей.
Окружения (& минимальный CI/CD):
Разработка была построена на основе пользовательских сценариев. Уже на старте проекта большая часть сценариев укладывалась в некий workflow. Но все же не все, и это обстоятельство усложняло нам разработку и не позволяло провести «глубокое проектирование».
В 2015 году наше приложение увидело продакшн. Промышленная эксплуатация показала, что нам не хватает гибкости в работе приложения, его разработке и в отправке изменений на prod-сервера. Мы хотели добиться High Availability (HA), Continuous Delivery (CD) и Continuous Integration (CI).
Вот проблемы, которые необходимо было решить для того, чтобы прийти к HI, CD, CI:
Мы стали решать все эти проблемы поочередно. И первое, за что взялись — это меняющиеся требования к продукту.
Вызов: Изменяющиеся требования к продукту и новые use cases.
Технологический ответ: Появился первый микросервис — вынесли часть бизнес-логики в отдельный war-файл и положили в Tomcat.
К нам пришла очередная задача вида: до конца недели обновить бизнес-логику в сервисе и мы приняли решение вынести эту часть в отдельный war-файл и положить в тот же Tomcat. Мы использовали Spring Boot для скорости конфигурирования и разработки.
Мы сделали небольшую бизнес-функцию, которая решала проблему с периодически изменяющимися параметрами пользователей. В случае изменения бизнес-логики нам бы не пришлось перезапускать весь Tomcat, терять наших пользователей на полчаса и перезагрузить только небольшую его часть.
После удачного вынесения логики по такому же принципу мы продолжили вносить изменения в приложение. И с того момента, когда к нам приходили задачи, которые кардинально меняли что-то внутри системы, мы выносили эти части отдельно. Таким образом, у нас постоянно копились новые микросервисы.
Основной подход, по которому мы начали выделять микросервисы — это выделение бизнес-функции или бизнес-услуга целиком.
Так у нас быстро отделились сервисы, интегрированные со сторонними системами, такими как 1C.
Вызов: Микросервисов уже 15. Проблема типизации.
Технический ответ: Spring Cloud Feign.
Проблемы не решились сами собой лишь потому, что мы начали разрезать наши решения на микросервисы. Более того, стали возникать новые проблемы:
Новые проблемы увеличили время перезапуска всего Tomcat при технических работах. Получается, мы усложнили себе работу.
Проблема с типизацией, конечно, возникла не сама собой. Скорее всего, в течение нескольких релизов мы её просто игнорировали, потому что находили эти ошибки ещё на этапах тестирования или во время разработки и успевали что-то предпринять. Но когда несколько ошибок были обнаружены уже совсем на полпути в продакшн и потребовали срочного исправления, мы ввели регламенты или начали использовать инструменты, которые решают эту проблему. Мы обратили внимание на Spring Cloud Feign — это клиентская библиотека для http-запросов.
github.com/OpenFeign/feign [2]
cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html [3]
Мы выбрали его, поскольку
— мало накладных расходов на внедрение в проект,
— сам генерировал клиента,
— можно использовать один интерфейс и на сервере, и на клиенте.
Он решил наши проблемы с типизацией тем, что мы формировали клиентов. И для контролеров наших сервисов мы использовали те же интерфейсы, что и для формирования клиентов. Так ушли проблемы с типизацией.
Вызов бизнеса: 18 микросервисов, теперь простои в работе системы недопустимы.
Технический ответ: изменение архитектуры, увеличение серверов.
У нас осталась проблема с простоем в работе и выкатыванием новых версий, осталась проблема с восстановлением сессии Tomcat и с освобождением ресурсов. Количество микросервисов же продолжало расти.
Процесс деплоя всех микросервисов занимал около часа. Периодически приходилось перезагружать приложение из-за проблемы с высвобождением ресурсов у tomcat. Не было простых способов делать это более оперативно.
Мы стали продумывать, как изменить архитектуру. Вместе с отделом инфраструктурных решений мы построили новое решение на основе того, что у нас уже было.
Архитектура изменила свой вид следующим образом:
Используемые технологии были такими:
Соответственно, когда приходил запрос от пользователя до сервисов в Tomcat, они просто запрашивали данные у MySQL. Те данные, которые требовали целостности, собирались со всех серверов и склеивались (все запросы были через API).
Применив этот подход, мы немного потеряли в консистентности данных, зато решили текущую задачу. Пользователь мог работать с нашим приложением в любых ситуациях.
Так были решены крупные проблемы. Ушёл простой в работе пользователей. Теперь они не чувствовали, когда мы накатывали обновление.
Вызов бизнеса: 23 микросервиса. Проблемы с консистентностью данных.
Техническое решение: запуск сервисов отдельно друг от друга. Улучшение мониторинга. Zuul и Eureka. Упростили разработку отдельных сервисов и их поставку.
Проблемы продолжали появляться. Так выглядел наш редеплой:
Подумав, мы решили, что нашу проблему решит запуск сервисов отдельно друг от друга — если мы будем запускать сервисы не в одном Tomcat, а каждый в своём на одном сервере.
Но появились другие вопросы: как сервисам теперь общаться между собой, какие порты должны быть открыты наружу?
Мы выбрали ряд портов и раздали их нашим модулям. Чтобы не было необходимости держать всю эту информацию о портах где-то в pom-файле или общей конфигурации, мы выбрали для решения этих задач Zuul и Eureka.
Eureka — service discovery [4]
Zuul — proxy [5] (для сохранения контекстных урлов, что были в Tomcat)
Eureka также улучшила наши показатели в High Availability /Fault Tolerance, поскольку теперь стало возможным общение между сервисами. Мы настроили так, что если в текущем дата-центре нет нужного сервиса, идти в другой.
Для улучшения мониторинга добавили из имеющегося стека Spring Boot Admin для понимания того, что на каком сервисе происходит.
Также мы начали переводить наши выделенные сервисы к stateless-архитектуре, чтобы избавиться от проблем деплоя нескольких одинаковых сервисов на одном сервере. Это дало нам горизонтальное масштабирование в рамках одного data-центра. Внутри одного сервера мы запускали разные версии одного приложения при обновлении, чтобы даже на нём не было никакого простоя.
Получилось, что мы приблизились к Continuous Delivery / Continuous Integration тем, что упростили разработку отдельных сервисов и их поставку. Теперь не нужно было опасаться, что поставка одного сервиса вызовет утечку ресурсов и придётся перезапускать весь сервис целиком.
Простой при выкатывании новых версий всё ещё остался, но не целиком. Когда мы обновляли поочерёдно несколько jar на сервере, это происходило быстро. И на сервере не возникало никаких проблем при обновлении большого количества модулей. Но перезапуск всех 25 микросервисов во время обновления занимал очень много времени. Хоть и быстрее, чем внутри Tomcat, который делает это последовательно.
Проблему с освобождением ресурсов мы решили также тем, что запускали всё с jar, а утечками или проблемами занимался системный Out of memory killer.
Вызов бизнеса: 28 микросервисов. Очень много информации, которой нужно управлять.
Техническое решение: Hazelcast.
Мы продолжили реализовывать свою архитектуру и поняли, что наша базовая бизнес-транзакция охватывает сразу несколько серверов. Нам было неудобно слать запрос в десяток систем. Поэтому мы решили использовать Hazelcast для event-мессаджинга и для системной работы с пользователями. Так же для последующих сервисов использовали его как прослойку между сервисом и базой данных.
Мы, наконец, избавились от проблемы с consistency наших данных. Теперь мы могли сохранять любые данные во все базы одновременно, не делая никаких лишних действий. Мы сказали Hazelcast, в какие базы данных он должен сохранять приходящую информацию. Он делал это на каждом сервере, что упростило нашу работу и позволило избавиться от шардинга. И тем самым мы перешли к репликации на уровне приложения.
Также теперь мы стали хранить сессию в Hazelcast и использовали его для авторизации. Это позволило переливать пользователей между серверами незаметно для них.
Вызов бизнеса: нужно ускорить выход обновлений в продакшн.
Техническое решение: конвейер развертывания нашего приложения, GitFlow для работы с кодом.
Вместе с количеством микросервисов развивалась и внутренняя инфраструктура. Мы хотели ускорить поставку наших сервисов до продакшна. Для этого мы внедрили новый конвейер развертывания нашего приложения и перешли к GitFlow для работы с кодом. СI начал собирать и прогонять тесты по каждому комиту, прогонять unit-тесты, интеграционные, складывать артефакты с поставкой приложения.
Чтобы делать это быстро и динамически, мы развернули несколько GitLab-раннеров, которые запускали все эти задачи по пушу разработчиков. Благодаря подходу GitLab Flow, у нас появилось несколько серверов: Develop, QA, Release-candidate и Production.
Разработка происходит следующим образом. Разработчик добавляет новый функционал в отдельной ветке (feature branch). После того, как разработчик закончил, он создает запрос на слияние его ветки с магистральной веткой разработки (Merge Request to Develop branch). Запрос на слияние смотрят другие разработчики и принимают его или не принимают, после чего происходит исправление замечаний. После слияния в магистральную ветку разворачивается специальное окружение, на котором выполняются тесты на поднятие окружения.
Когда все эти этапы закончены, QA инженер забирает изменения к себе в ветку “QA” и проводит тестирование по ранее написанным тест-кейсам на фичу и исследовательское тестирование.
Если QA инженер одобряет проделанную работу, тогда изменения переходят в ветку Release-Candidate и разворачиваются на окружении, которое доступно для внешних пользователей. На этом окружении заказчик производит приемку и сверку наших технологий. Затем мы перегоняем всё это в Production.
Если на каком-то этапе находятся баги, то именно в этих ветках мы решаем эти проблемы и их мёрджим в Develop. Также сделали небольшой плагин, чтобы Redmine мог сообщать нам, на каком этапе находится фича.
Это помогает тестировщикам смотреть, на каком этапе нужно подключаться к задаче, а разработчикам — править баги, потому что они видят, на каком этапе произошла ошибка, могут пойти в определенную ветку и воспроизвести её там.
Вызов бизнеса: переключение между серверами без простоя.
Техническое решение: Упаковка в Kubernetes.
Сейчас по окончанию деплоймента технические специалисты докладывают jer-ки на PROD-сервера и перезапускают их. Это не очень удобно. Мы хотим автоматизировать работу системы и дальше, внедрив Kubernetes и связав его с data-центром, обновляя их и разом накатывая.
Чтобы перейти к этой модели, нам необходимо закончить следующие работы.
Автор: EastBanc Technologies
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/agile/276731
Ссылки в тексте:
[1] MVP: https://ru.wikipedia.org/wiki/%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE_%D0%B6%D0%B8%D0%B7%D0%BD%D0%B5%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9_%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82
[2] github.com/OpenFeign/feign: https://github.com/OpenFeign/feign
[3] cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html: https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html
[4] Eureka — service discovery: https://github.com/Netflix/eureka
[5] Zuul — proxy: https://github.com/Netflix/zuul
[6] Источник: https://habrahabr.ru/post/352618/?utm_source=habrahabr&utm_medium=rss&utm_campaign=352618
Нажмите здесь для печати.