Как перейти на микросервисы и не разломать production

в 7:34, , рубрики: agile, eureka, feign, Hazelcast, high availability, mvp, product management, production, zuul, Блог компании EastBanc Technologies, высокая производительность, микросервис, Микросервисная архитектура, микросервисы, Управление продуктом, шардинг

Сегодня расскажем, как переводили на микросервисы монолитное решение. Через наше приложение круглосуточно проходит от 20 до 120 тысяч транзакций в сутки. Пользователи работают в 12 часовых поясах. В то же время функционал добавлялся много и часто, что довольно сложно делать на монолите. Вот почему системе требовались устойчивая работа в режиме 24/7, то есть HighLoad, High Availability и Fault Tolerance.

Мы развиваем этот продукт по модели MVP. Архитектура менялась в несколько этапов вслед за требованиями бизнеса. Первоначально не было возможности сделать всё и сразу, потому что никто не знал, как должно выглядеть решение. Мы двигались по модели Agile, итерациями добавляя и расширяя функциональность.

Как перейти на микросервисы и не разломать production - 1

Первоначально архитектура выглядела следующим образом: у нас был MySql c единственным war, Tomcat и Nginx для проксирования запросов от пользователей.

Как перейти на микросервисы и не разломать production - 2

Окружения (& минимальный CI/CD):

  • Dev — деплой по Push в develop,
  • QA — раз в сутки с develop,
  • Prod — по кнопке с master,
  • Запуск интеграционных тестов вручную,
  • Всё работает на Jenkins.

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

Как перейти на микросервисы и не разломать production - 3

В 2015 году наше приложение увидело продакшн. Промышленная эксплуатация показала, что нам не хватает гибкости в работе приложения, его разработке и в отправке изменений на prod-сервера. Мы хотели добиться High Availability (HA), Continuous Delivery (CD) и Continuous Integration (CI).

Вот проблемы, которые необходимо было решить для того, чтобы прийти к HI, CD, CI:

  • простой при выкатывании новых версий — cлишком долго происходил deploy приложения,
  • проблема с меняющимися требованиями к продукту и новые user cases — слишком много времени уходило на тестирование и проверки даже при небольших фиксах,
  • проблема с восстановлением сессий у Tomcat: session management для системы бронирования и сторонних сервисов, при перезапуске приложения сессия не восстанавливалась силами Tomcat,
  • проблемы с высвобождением ресурсов: приходилось рано или поздно перезагружать Tomcat, происходили memory leak.

Мы стали решать все эти проблемы поочередно. И первое, за что взялись — это меняющиеся требования к продукту.

Первый микросервис

Вызов: Изменяющиеся требования к продукту и новые use cases.
Технологический ответ: Появился первый микросервис — вынесли часть бизнес-логики в отдельный war-файл и положили в Tomcat.

Как перейти на микросервисы и не разломать production - 4

К нам пришла очередная задача вида: до конца недели обновить бизнес-логику в сервисе и мы приняли решение вынести эту часть в отдельный war-файл и положить в тот же Tomcat. Мы использовали Spring Boot для скорости конфигурирования и разработки.

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

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

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

Так у нас быстро отделились сервисы, интегрированные со сторонними системами, такими как 1C.

Первая проблема — типизация

Вызов: Микросервисов уже 15. Проблема типизации.
Технический ответ: Spring Cloud Feign.

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

  • проблема типизации и версионирования в Dto между модулями,
  • как задеплоить не один war-файл в Tomcat, а множество.

Новые проблемы увеличили время перезапуска всего Tomcat при технических работах. Получается, мы усложнили себе работу.

Проблема с типизацией, конечно, возникла не сама собой. Скорее всего, в течение нескольких релизов мы её просто игнорировали, потому что находили эти ошибки ещё на этапах тестирования или во время разработки и успевали что-то предпринять. Но когда несколько ошибок были обнаружены уже совсем на полпути в продакшн и потребовали срочного исправления, мы ввели регламенты или начали использовать инструменты, которые решают эту проблему. Мы обратили внимание на Spring Cloud Feign — это клиентская библиотека для http-запросов.

github.com/OpenFeign/feign
cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html

Мы выбрали его, поскольку

— мало накладных расходов на внедрение в проект,
— сам генерировал клиента,
— можно использовать один интерфейс и на сервере, и на клиенте.

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

Простои. Бой первый. Работоспособность

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

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

Процесс деплоя всех микросервисов занимал около часа. Периодически приходилось перезагружать приложение из-за проблемы с высвобождением ресурсов у tomcat. Не было простых способов делать это более оперативно.

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

Как перейти на микросервисы и не разломать production - 5

Архитектура изменила свой вид следующим образом:

  • горизонтально разделили наше приложение на несколько data-центров,
  • добавили Filebeat на каждый сервер,
  • добавили отдельный сервер для ELK, поскольку росло количество транзакций и логов,
  • несколько серверов haproxy + Tomcat + Nginx + MySQL (так мы обеспечивали High Availability).

Используемые технологии были такими:

  • Haproxy занимается роутингом и балансировкой между серверами,
  • Nginx отвечает за раздачу статики, tomcat был сервером приложений,
  • Особенностью решения стало то, что MySQL на каждом из серверов не знает о существовании своих других MySQL’ей,
  • Из-за проблемы latency между датацентрами репликация на уровне MySQL была невозможна. Поэтому мы решили реализовать шардинг на уровне микросервисов.

Соответственно, когда приходил запрос от пользователя до сервисов в Tomcat, они просто запрашивали данные у MySQL. Те данные, которые требовали целостности, собирались со всех серверов и склеивались (все запросы были через API).

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

  • Даже если один из серверов падал, у нас оставалось ещё 3-4, которые, поддерживали работоспособность всей системы.
  • Мы хранили бекапы не на серверах в том же дата-центре, в котором они делались, а в соседних. Это помогало нам с disaster recovery.
  • Fault tolerance решалась также за счет нескольких серверов.

Так были решены крупные проблемы. Ушёл простой в работе пользователей. Теперь они не чувствовали, когда мы накатывали обновление.

Простои. Бой второй. Полноценность

Вызов бизнеса: 23 микросервиса. Проблемы с консистентностью данных.
Техническое решение: запуск сервисов отдельно друг от друга. Улучшение мониторинга. Zuul и Eureka. Упростили разработку отдельных сервисов и их поставку.

Проблемы продолжали появляться. Так выглядел наш редеплой:

  • У нас не было консистентности данных при редиплое, поэтому часть функционала (не самого важного) отходила на второй план. К примеру, при накатывании нового приложения статистика работала неполноценно.
  • Нам приходилось выгонять пользователей с одного сервера на другой, чтобы перезапустить приложение. Это тоже занимало порядка 15-20 минут. Вдобавок ко всему, пользователям приходилось перелогиниваться при переходе с сервера на сервер.
  • Также мы всё чаще перезапускали Tomcat из-за роста количества сервисов. И теперь приходилось следить за большим количеством новых микросервисов.
  • Время редиплоя выросло пропорционально количеству сервисов и серверов.

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

Как перейти на микросервисы и не разломать production - 6

Но появились другие вопросы: как сервисам теперь общаться между собой, какие порты должны быть открыты наружу?

Мы выбрали ряд портов и раздали их нашим модулям. Чтобы не было необходимости держать всю эту информацию о портах где-то в pom-файле или общей конфигурации, мы выбрали для решения этих задач Zuul и Eureka.

Eureka — service discovery
Zuul — proxy (для сохранения контекстных урлов, что были в 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-мессаджинга и для системной работы с пользователями. Так же для последующих сервисов использовали его как прослойку между сервисом и базой данных.

Как перейти на микросервисы и не разломать production - 7

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

Также теперь мы стали хранить сессию в Hazelcast и использовали его для авторизации. Это позволило переливать пользователей между серверами незаметно для них.

От микросервисов к CI/CD

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

Вместе с количеством микросервисов развивалась и внутренняя инфраструктура. Мы хотели ускорить поставку наших сервисов до продакшна. Для этого мы внедрили новый конвейер развертывания нашего приложения и перешли к GitFlow для работы с кодом. СI начал собирать и прогонять тесты по каждому комиту, прогонять unit-тесты, интеграционные, складывать артефакты с поставкой приложения.

Как перейти на микросервисы и не разломать production - 8

Чтобы делать это быстро и динамически, мы развернули несколько GitLab-раннеров, которые запускали все эти задачи по пушу разработчиков. Благодаря подходу GitLab Flow, у нас появилось несколько серверов: Develop, QA, Release-candidate и Production.

Разработка происходит следующим образом. Разработчик добавляет новый функционал в отдельной ветке (feature branch). После того, как разработчик закончил, он создает запрос на слияние его ветки с магистральной веткой разработки (Merge Request to Develop branch). Запрос на слияние смотрят другие разработчики и принимают его или не принимают, после чего происходит исправление замечаний. После слияния в магистральную ветку разворачивается специальное окружение, на котором выполняются тесты на поднятие окружения.

Когда все эти этапы закончены, QA инженер забирает изменения к себе в ветку “QA” и проводит тестирование по ранее написанным тест-кейсам на фичу и исследовательское тестирование.

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

Если на каком-то этапе находятся баги, то именно в этих ветках мы решаем эти проблемы и их мёрджим в Develop. Также сделали небольшой плагин, чтобы Redmine мог сообщать нам, на каком этапе находится фича.

Как перейти на микросервисы и не разломать production - 9

Это помогает тестировщикам смотреть, на каком этапе нужно подключаться к задаче, а разработчикам — править баги, потому что они видят, на каком этапе произошла ошибка, могут пойти в определенную ветку и воспроизвести её там.

Дальнейшее развитие

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

Как перейти на микросервисы и не разломать production - 10

Сейчас по окончанию деплоймента технические специалисты докладывают jer-ки на PROD-сервера и перезапускают их. Это не очень удобно. Мы хотим автоматизировать работу системы и дальше, внедрив Kubernetes и связав его с data-центром, обновляя их и разом накатывая.

Чтобы перейти к этой модели, нам необходимо закончить следующие работы.

  • Привести наши текущие решения к stateless-архитектуре, чтобы пользователь мог отправлять запросы на все сервера без разбора. Некоторые из наших сервисов ещё поддерживают какие-то сессионные данные. Эта работа касается и репликации данных базы данных.
  • Также мы должны распилить последний маленький монолит, который содержит в себе несколько бизнес-процессов. Это и приведёт нас к последнему главному шагу — Continuous Delivery.

P.S. Что изменилось с переходом на микросервисы

  • Мы избавились от проблемы меняющихся требований.
  • Избавились от проблемы восстановления сессий у Tomcat тем, что перенесли их в Hazelcast.
  • При перебрасывании пользователей с одного сервера на другой им не приходится перелогиниваться.
  • Решили все проблемы с высвобождением ресурсов, переложив их на плечи операционной системы.
  • Проблемы типизации и версионирования решились благодаря Feign.
  • Уверенно движемся в сторону Continuous Delivery c помощью Gitlab Pipelines.

Автор: EastBanc Technologies

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js