Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет, Хабр! На связи Андрей Чернов, Java-архитектор микросервисов в СберТехе. Эта статья — продолжение материала о том, как мы развиваем Platform V SessionsData — высокопроизводительное распределённое in-memory хранилище для общего контекста сессионных запросов key-value, которое СберБанк Онлайн использует в качестве микросервиса на своём server side.
В первой части мы говорили о том, почему решили создать собственный микросервис и как он помогает справляться с нагрузкой СберБанк Онлайн. В этой разберём, как мы достигаем высокой доступности сервиса. Будет и третья часть: расскажем, какие доработки помогут нам и дальше развивать Platform V SessionsData.
Как Platform V SessionsData достигает высокой доступности
Стандарты СберБанк Онлайн требуют от микросервисов доступности на уровне «четырёх девяток». Это значит, что максимально допустимое время простоя — чуть более 52 минут в год. Platform V SessionsData — критически важный для функционирования СберБанк Онлайн сервис, поэтому к нам эти требования предъявляются особенно жёстко.
Чтобы поддерживать высокую доступность сервиса, мы отказались от синхронных обращений к базе данных и от развёртывания мастер-хранилища в контейнерах. Вместо серверов приложений от IBM запустили мастера на Tomcat, встроенном в Spring Boot, и прямо сейчас разрабатываем репликацию данных в мастер-хранилище.
Но начать рассказ стоит с балансировки нагрузки (load balancing) и «прилипания сессий» (true sticky) к узлам мастер-хранилища.
Балансировка нагрузки и прилипание сессий
У современных серверов наработка на отказ составляет около 6,5 лет. Если при отказе сервер меняют за сутки — то есть оперативно, — уровень доступности снижается до 99,96 %, без учёта выключения машин для планового обслуживания. Этого показателя уже недостаточно, поэтому нам требовалось проверенное надёжное решение для увеличения доступности сервиса.
Задачу помогло решить наличие нескольких серверов для мастер-хранилища и балансировка нагрузки на эти узлы с помощью Round robin. Для этого мы используем шлюз cобственной разработки, основанный на Nginx и доработанный до уровня enterprise в области безопасности и отказоустойчивости.
Если какому-то из мастеров станет плохо, он будет удалён из балансировки. С этим нам помогает active health check Nginx, доработанный в Platform V. Он работает как readiness probe в Kubernetes: шлюз периодически опрашивает балансируемые узлы и удаляет проблемные из балансировки.
Сейчас в СберБанк Онлайн 24 мастер-узла в одной группе балансировки. Вероятность того, что все они одновременно выйдут из строя, ничтожно мала. Поэтому требование доступности в «четыре девятки» соблюдается с запасом.
Но не всё так просто. Наш сервис — stateful by design. Балансировка нагрузки с помощью Round robin применима только при создании сессии, когда запросам всё равно, на какой мастер-узел они придут. И это хорошо: создаваемые сессии равномерно распределяются по узлам. А вот запросам в рамках уже созданной сессии не всё равно. Для них мы используем «прилипание» к узлу мастера, в памяти которого хранится сессия. Slave прикрепляет к запросу «липкую» cookie, чтобы шлюз отправлял этот запрос по конкретной сессии в конкретный мастер. Причём это true sticky: изменение количества балансируемых узлов не ломает ранее установленные прилипания. Это позволяет масштабировать мастер, добавляя новые узлы «на горячую», не прерывая существующих сессий.
Из-за использования true sticky у нас нет горизонтальных связей между узлами мастера. Как следствие, задержка становится более предсказуемой, а это важное достоинство.
Асинхронная работа с базой данных
Мы продолжили работать над высокой доступностью сервиса. На очереди — изменение подхода к работе с базой данных.
В первой части я рассказывал, что мастера Platform V SessionsData хранят в БД только список сессий без самих данных. БД нужна, чтобы администратор мог видеть в UI-консоли список активных сессий, фильтровать их, искать и совершать с ними действия, например, удалять. Для отображения в админке в базе хранится ID сессии, момент её создания, информация об узле мастера, где хранятся данные сессии, и другая информация.
Раньше сессию в базу мастера вставляли синхронно. Пока вставка не была выполнена, потребитель не получал ответа. А если вставить сессию не получалось, выдавалась ошибка её создания. Получается, если БД откажет, то сервис не сможет создать ни одной сессии. По сути, это простой. Технически Platform V SessionsData может обслуживать потребителей и при недоступной базе, просто в админке не будет видно активных сессий. Это меньшее зло по сравнению с простоем, поэтому мы решили вставлять записи в БД асинхронно и пачками.
Теперь мастер при создании сессии не делает записей в БД, а сохраняет необходимую информацию для вставки в очередь в памяти. Мы добавили в мастер асинхронную задачу, которая периодически извлекает из очереди пачку сессий — по умолчанию максимум 300 — и вставляет записи в БД. Если не получается, то журналируется ошибка и попытка повторяется позже. Если сессию не удалось вставить три раза подряд, она удаляется из очереди, чтобы не тратить память мастера.
Периодичность асинхронной вставки — четверть секунды, поэтому администратор сервиса не замечает задержек между созданием сессии и её появлением в админке. Аналогичное решение работает и при удалении записей из БД, для этого используется отдельная очередь в памяти мастера. Благодаря такой асинхронной работе мы ускорили создание и удаление сессий и перестали зависеть от отказов БД.
Хранилище не в контейнере
Дальше нам пришлось столкнуться с рядом задач. Изначально наши потребители в СберБанк Онлайн развёртывались на Java EE-серверах приложений от IBM, как и наши мастеры. Наш сервис — stateful by design, и нам противопоказан перезапуск узлов с мастер-хранилищем. А в Kubernetes, куда стали переходить пользователи, гораздо больше причин для перезапуска подов, чем в статической инфраструктуре на виртуалках. Например, автомасштабирование, вытеснение сервиса с узла из-за развёртывания другого сервиса с более высоким PriorityClass или классом Quality of Service (QoS), и многое другое. Но Kubernetes изначально спроектирован для развёртывания stateless-сервисов, а механизмы поддержки stateful обычно не рекомендуется использовать в крупных рабочих средах.
Мы оценили ситуацию и решили отказаться от развёртывания мастер-хранилища в контейнерах. При этом мы не могли запретить потребителям идти в облако, и выполнили ряд доработок:
Выпустили версию slave sidecar для развёртывания в контейнере. Переписали на Spring Boot и запустили на встроенном Tomcat вместо Java EE-сервера приложений от IBM. Внутренняя логика работы slave принципиально не изменилась.
Перешли заодно на Java 11 в slave, так как после ухода с серверов приложений от IBM больше не нужно было сидеть на Java 8.
Разобрались, как настроить липкие сессии на Istio Ingress Gateway, и написали рекомендации для наших облачных потребителей. Благодаря липкости сессии сохранили эффективность использования кеша в slave sidecar.
Мы дали потребителям возможность развёртываться в контейнере и при этом не ухудшили уровень доступности Platform V SessionsData — мастер-хранилище у нас по-прежнему развёртывалось в статической инфраструктуре.
Независимость от вендоров
Ещё одна задача, с которой мы столкнулись, связана с импортозамещением. Расскажу, как мы её решили и избавились от потенциального простоя.
Ещё до ухода западных вендоров Сбер начал переходить на собственные инновационные разработки и постепенно отказался от Java EE-серверов приложений от IBM, которые использовал для развёртывания мастер-хранилища. Как мы обсудили выше, размещать мастера в Kubernetes было нельзя: наш сервис stateful by design. Поэтому мы развернули мастер-хранилище непосредственно на виртуалках с Linux, поменяв Java EE на Spring Boot. Теперь вместо серверов приложений от IBM мастеры запускаются на Tomcat, встроенном в Spring Boot. В мастере, как и в случае со slave sidecar, мы перешли на Java 11.
Так как мастер теперь — Spring Boot-приложение, на виртуалке мы запускаем его командой java -jar, используя Spring Boot-овый fat jar. Нам потребовался запускающий скрипт, который перед запуском мастера готовит необходимые для запуска параметры и секреты. По сути, скрипт создаёт Spring Boot-окружение мастера из файлов, установленных на виртуалку.
А раз уж мы Java-программисты, то и запускающий скрипт написали на Java. Воспользовались возможностью Java 11, которая позволяет без предварительной компиляции запускать Java-программу, состоящую из единственного исходника. В нашем случае это Run.java.
Процесс JVM-мастера запускали стандартным классом java.lang.ProcessBuilder. Вот сильно упрощенный код Java-скрипта:
А чтобы не приходилось вручную поднимать процесс мастера после форс-мажоров, обновлений, перезапуска Linux и прочего, наш запускающий скрипт инициируется службой Linux. Systemd юнит-файл следующий:
Обратите внимание на параметр Restart=on-failure
. Благодаря ему наш мастер-сервис поднимается сам, какой бы ни была причина остановки.
Таким образом, развёртывание мастер-хранилища непосредственно на виртуалках помогло нам избавиться от потенциального простоя из-за блокировки вендорского решения, и заодно сэкономить.
Подводим итоги
Для достижения высокой доступности в Platform V SessionsData мы используем целый ряд архитектурных решений и регулярно анализируем потенциальные проблемы. Результат: ни одного отказа за четыре года эксплуатации.
В этой статье я рассказал, как мы поддерживаем доступность сервиса на уровне «четырёх девяток». Вот основные выводы, которыми я хочу поделиться:
При проектировании сервиса избегайте «бутылочных горлышек», способных при отказе привести к простою.
Не развёртывайте в облаке stateful-сервисы. По крайней мере, в эксплуатации. Системы управления контейнерами хорошо справляются со stateless-сервисами, а для stateful они изначально не предназначены.
Подумайте о переходе с западного ПО на отечественное.
В следующей, заключительной части расскажу, какие доработки помогут нам сделать Platform V SessionsData ещё лучше. Спасибо за внимание и до встречи!