Привет, Хабр! На связи Андрей Чернов, 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 обычно не рекомендуется использовать в крупных рабочих средах.

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

  1. Выпустили версию slave sidecar для развёртывания в контейнере. Переписали на Spring Boot и запустили на встроенном Tomcat вместо Java EE-сервера приложений от IBM. Внутренняя логика работы slave принципиально не изменилась.

  2. Перешли заодно на Java 11 в slave, так как после ухода с серверов приложений от IBM больше не нужно было сидеть на Java 8. 

  3. Разобрались, как настроить липкие сессии на 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 ещё лучше. Спасибо за внимание и до встречи!

Комментарии (9)


  1. VladimirFarshatov
    31.05.2023 08:20
    -1

    Лучше расскажите зачем ваш сберонлайн просит 300+ запросов со страницы.. что там такого? Какой-то треш и угар, особенно на деревенских скоростях соединения в 56кбод .. :(


    1. ChernovAF Автор
      31.05.2023 08:20

      Ребят, давайте по делу. Согласен, это боль, но она не относится к теме статьи.


      1. VladimirFarshatov
        31.05.2023 08:20
        -1

        Мало того, что минусуетеЮ, так ещё и врете внаглую! Вопрос НАПРЯМУЮ относится как в вашей "новой" микросервисной архитектуре, так и тематике этой статьи. Сначала насоздавали себе проблем, потом выпускаете статью "как мы себе упростили"?

        Что "упростили"-то? Уберите эту безмозглую тучу из 300+ запросов, и опаньки .. много "нагрузки" слиняет в кусты.. Может Вам ПОКАЗАТЬ список этих 300+ запросов, да раскодировать те JS, которые там запрашиваются на потеху читателям?

        .. sbsans.woff2 dhjlt вроде бы шрифт .. с какого лешего он подгружается ТРИЖДЫ?

        .. index.js -- 6(шесть!!!) раз подряд.

        И это только зашел по ключу, ещё ничего даже нажать не успелось. Каждая(!!!) иконка грузится .. отдельно и самостоятельно. Про различные собственные и сторонние(!) метрики, ваще молчу..

        позорище, а не разработка..


        1. ChernovAF Автор
          31.05.2023 08:20

          Статья про backend.

          Отставить негатив про frontend!


          1. VladimirFarshatov
            31.05.2023 08:20

            Правая рука не знает что творит левая? ;) Бывает..


  1. puzo
    31.05.2023 08:20
    +1

    Интересно: при балансировке учитывается ли текущая очередь задач мастер-узла?
    по тексту понял, что сначала RoundRobin, а далее sticky session. Или тут без магии?


    1. ChernovAF Автор
      31.05.2023 08:20

      При создании каждой новой сессии запросы распределяются по мастер-узлам равномерно с помощью RoundRobin балансировки.

      А запросы в рамках уже созданной сессии (прочитать/записать данные) идут строго в тот мастер-узел, где сессия была создана. Вот здесь уже используется sticky session.

      В продакшене постоянно есть нескончаемый поток как первых запросов, так и вторых, поэтому RoundRobin и sticky session работают параллельно (в разных запросах).


      1. puzo
        31.05.2023 08:20
        +1

        тут как раз все понятно. но процессорная стоимость задачи может быть разная, а следовательно может сложится так, что один из мастер - узлов обрабатывает 100 "тяжелых" задач при этом остальные инстансы мастер-узлов в этот же момент имеют - 0 задач. Тут кажется удачным, пока исключить мастер-узел_100_задач из балансировки на время.
        или такой проблемы нет т.к. асинхронность позволяет об этом не думать?


        1. ChernovAF Автор
          31.05.2023 08:20

          Такой проблемы нет, потому что:

          1. Очень много сессий в online (в СберБанк Онлайн полмиллиона-миллион). И все они равномерно распределены по мастер-узлам.

          2. Абсолютное большинство сессий всё-таки более-менее однородные (в пляне "тяжести" создаваемой нагрузки).

          Поэтому нагрузка, создаваемая такими сессиями, очень равномерно нагружает CPU на всех мастер-узлах кластера.