Давайте представим, что мы уже написали наше Spring Boot приложение, и оно успешно работает на протяжении некоторого времени. И теперь мы понимаем, что для того, чтобы справиться с возросшей нагрузкой и повысить доступность, нам необходимо запустить несколько новых инстансов сервиса. Но мы не подумали об этом заранее, на этапе разработки.
Так что же может помешать нам просто взять и запустить еще несколько инстансов?
Использование планировщиков.
Использование WebSocket.
Пользовательские сессии, хранящиеся в памяти.
Кэш приложения - это может быть либо простой параллельный
hashmapв компонентах, либо spring кэш.
Что ж, все это можно легко адаптировать с помощью соответствующих инструментов.
Планировщики
Использование распределенных планировщиков не самая тривиальная задача, которая, к тому же, требует дополнительное время для настройки и правки кода. Наша цель - как можно быстрее реализовать перехода от 1 до N одновременно работающих инстансов.
К счастью, существует библиотека Shedlock с интеграцией Spring, которая позволяет осуществить наш замысел с помощью всего лишь пары аннотаций.
Shedlock создает таблицу в вашей базе данных (поддерживается почти любое хранилище) и использует ее для координации инстансов так, что единовременно только один инстанс может использовать конкретный планировщик.
Нам необходимо включить Shedlock с блокировкой по умолчанию на 5 минут (максимум).
@Configuration @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "5m") public class ApplicationConfiguration { }
И добавить аннотацию ShedLock'а с указанием названия конкретного планировщика.
@Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "myTask") public void run() { // do something }
Таким образом, в следующий цикл планировщика каждый инстанс будет пытаться записать строку о себе в таблицу блокировок, и первый, кто успеет это сделать, продолжит выполнение задачи. Все остальные пропускают цикл.
Блокировка задачи в нашем случае будет висеть максимум 5 минут. По завершении задачи инстанс снимает блокировку в таблице. Если по истечении 5 минут блокировка не была снята, другие инстансы будут считать, что этот инстанс завис во время выполнения задачи.
Websocket
Если пользователи нашего приложения могут отправлять друг другу сообщения и для реализации этой функции мы используем WebSocket, возможна такая ситуация, когда пользователь Вова подключен к инстансу A, а пользователь Алиса подключена к инстансу B. Мы оказываемся в ситуации, когда инстансы должны каким-то образом самостоятельно понимать, куда им отправлять сообщения и обмениваться ими между собой.
Написание такого уровня - это кропотливая работа, которая значительно замедляет масштабирование приложения. Вместо этого мы можем использовать готовую реализацию Spring BrokerRelay, которая использует внешнего брокера (RabbitMQ, ActiveMQ и т. д.) для обработки WebSocket, управления подписками и т. д.
@Configuration public class WebSocketConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableStompBrokerRelay("/topic", "/queue") .setClientLogin("guest") .setClientPasscode("guest") .setSystemLogin("guest") .setSystemPasscode("guest") .setRelayHost("127.0.0.1") .setRelayPort(61613); } }
В этом случае все сообщения в /topic и /queue будут пересылаться внешнему брокеру. С детальной схемой взаимодействия можно ознакомиться в документации Spring.
Пользовательские сессии
Разумеется, для каждого инстанса приложения сессии должны быть общими. К счастью, для их хранения подойдет любая база данных. Для какого-нибудь непопулярного хранилища вам нужно написать свой собственный SessionRepository.
Но для JDBC, достаточно добавить всего одну зависимость.
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-jdbc</artifactId> </dependency>
И установить свойство в application.properties:
spring.session.store-type=jdbc
Однако, в данном случае, не стоит забывать, что данные сессии будут храниться в виде блобов, и иногда во время обновления Spring, вам необходимо будет чистить сессии из-за проблем с десериализацией старых объектов.
Кэш приложения
Мы бы не хотели, чтобы у каждого инстанса приложения был собственный кэш. В противном случае результаты запроса могут зависеть от того, куда балансировщик нагрузки направляет пользователя.
Чтобы все инстансы использовали общий кэш, стоит подключить внешнее хранилище, например Redis, Apache Ignite, Hazelcast и т.д.
В случае использования Spring-Cache или Redis достаточно добавить одну зависимость.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
После этого с аннотацией @EnableCaching, Spring Boot автоматически активирует Redis в качестве конфигурации по умолчанию.
Например, его можно настроить так, чтобы установить разное время работы для каждого модуля.
@Bean public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() { return builder -> builder .withCacheConfiguration(“AccountCache", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(60))) .withCacheConfiguration(“ProductCache", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120))); }
Заключение
Таким образом, даже если вы не задумывались о масштабировании при создании приложения, существует набор инструментов, которые помогут сделать возможным переход от 1 к N одновременно запущенных инстансов без переписывания бизнес-логики самого сервиса.
Материал подготовлен нашим экспертом - Александром Коженковым и опубликован в преддверии старта курса «Microservice Architecture».
Всех желающих приглашаем на открытый урок «Паттерны аутентификации и авторизации». На занятии будет рассказано про различные паттерны аутентификации и авторизации. Рассмотрена сессионная аутентификация на основе кук и на основе токенов (jwt), работа identity провайдеров.
>> Регистрация
