Менеджер HTTP-сессий
Менеджер HTTP-сессий хранит контекст пользователя между запросами к Tomcat.
Авторизация выполняется отдельно в AuthFilter; менеджер отвечает за жизненный цикл
HttpSession, её атрибуты и срок действия.
ERP поддерживает два варианта:
-
стандартный
org.apache.catalina.session.StandardManagerхранит сессии в памяти одного инстанса; -
ru.bgcrm.session.CustomSessionManagerсохраняет сессии в Redis и позволяет нескольким инстансам работать с одним хранилищем.
По умолчанию используется StandardManager. Кастомный менеджер включается только при запуске
приложения и требует доступного Redis.
Как работает кастомный менеджер
При включении SessionManagerInitializer читает redisson.conf из рабочего каталога,
создаёт RedissonStore и устанавливает CustomSessionManager в контекст Tomcat.
Если конфигурация Redisson некорректна или соединение создать не удалось, запуск ERP
завершается ошибкой.
SessionStoreValve сохраняет уже существующую сессию после обработки каждого запроса.
Сессия не создаётся специально ради сохранения. Запросы, URL которых содержит .js,
.css или .vue, пропускаются.
В Redis каждая сессия хранится в map с ключом session:erp:<sessionId>. Вместе с
пользовательскими атрибутами записываются время создания и maxInactiveInterval.
TTL ключа равен maxInactiveInterval, поэтому просроченные сессии удаляет Redis.
Не сохраняются:
-
атрибут локали Struts
org.apache.struts.action.LOCALE; -
значения, реализующие маркерный интерфейс
IgnoreOnPersistSessionAttribute.
Остальные атрибуты должны сериализоваться настроенным Jackson-кодеком. После добавления в сессию нового типа нужно проверить его запись и чтение через Redis, в том числе после перезапуска ERP.
Конфигурация ERP
Параметры server.customManager.* читаются из общей конфигурации ERP при запуске.
Изменение значений в административном интерфейсе не заменяет уже созданный менеджер:
для применения нужен перезапуск инстанса.
| Параметр | По умолчанию | Назначение |
|---|---|---|
|
|
Включает |
|
|
Имя текущего инстанса в сообщениях об изменении сессий. |
|
|
|
|
|
|
|
|
Канал уведомлений об обновлении и удалении сессии: |
|
пустой список |
Идентификаторы инстансов, чьи каналы нужно слушать в режиме |
Значения перечислений задаются прописными буквами. Неизвестное значение останавливает инициализацию менеджера.
Минимальная конфигурация одного инстанса:
server.customManager.enabled=true
server.customManager.nodeId=erp0
server.customManager.readMode=MEMORY
server.customManager.writeMode=ASYNC
server.customManager.publishMode=GLOBAL
Конфигурация Redisson
Файл redisson.conf находится в рабочем каталоге ERP. Это YAML-конфигурация Redisson;
менеджер не читает адрес Redis из bgcrm.properties.
Минимальный пример для одного Redis-сервера:
singleServerConfig:
address: "redis://redis.example.org:6379"
database: 0
connectTimeout: 10000
timeout: 2000
retryAttempts: 3
retryInterval: 1000
Полный набор параметров соединения определяется Redisson. После изменения файла нужен перезапуск ERP.
Согласование сессий между инстансами
Redis Pub/Sub передаёт только уведомление с идентификатором сессии. Сами атрибуты
остаются в session:erp:<sessionId>.
При обновлении сессии другой инстанс удаляет её из локального кэша. При следующем запросе
актуальная версия загружается из Redis. При удалении сессии найденная локальная копия
завершается через expire().
Режимы публикации:
-
GLOBAL— изменения публикуются в общий канал. Каждый кастомный менеджер подписывается на него автоматически;listenersне требуется; -
NODE— изменения публикуются в каналnodeIdотправителя. Каждый другой инстанс должен перечислить этотnodeIdвlisteners; -
DISABLED— уведомления не публикуются. Режим подходит только там, где не требуется инвалидировать локальные копии на других инстансах.
Для трёх инстансов в режиме NODE конфигурация подписок симметрична:
# erp0
server.customManager.nodeId=erp0
server.customManager.listeners=erp1,erp2
# erp1
server.customManager.nodeId=erp1
server.customManager.listeners=erp0,erp2
# erp2
server.customManager.nodeId=erp2
server.customManager.listeners=erp0,erp1
Сообщения, отправленные самим инстансом, игнорируются. При NODE с пустым listeners
межсерверной инвалидации локального кэша не будет.
Выбор режимов
| Режим | Преимущество | Особенность |
|---|---|---|
|
Повторные запросы обычно не читают Redis. |
Для нескольких инстансов нужна корректная публикация инвалидаций. |
|
Каждый запрос получает состояние из общего хранилища. |
После запроса локальная копия удаляется; возрастает нагрузка и зависимость от Redis. |
|
Запрос не ждёт сетевую запись. |
При ошибке сохранения запрос уже завершён. В сочетании со |
|
К моменту успешного завершения сохранения данные уже находятся в Redis. |
Задержка и недоступность Redis влияют на пользовательский запрос. |
Для кластера с локальным кэшем обычно используют MEMORY и либо GLOBAL, либо полностью
заполненную схему NODE/listeners. Выбор ASYNC или SYNCHRONIZED зависит от допустимого
риска потери последнего изменения сессии и требований к задержке.
Ограничения текущей реализации
-
Единственное внешнее хранилище, которое создаёт
SessionManagerInitializer, — Redis. Реализаций для базы данных или файловой системы в модуле нет. -
Комбинацию
writeMode=ASYNCиpublishMode=DISABLEDиспользовать нельзя:AsyncWriteRedissonAdapterпосле успешной записи или удаления безусловно обращается к объекту канала публикации, который в режимеDISABLEDне создаётся. Если публикация не нужна, используйтеwriteMode=SYNCHRONIZED. -
Redis Pub/Sub не хранит сообщения. Инстанс, который был отключён от Redis в момент обновления, не получит пропущенную инвалидацию локальной копии. Это особенно важно для режима
MEMORY. -
Удаление ключа самим Redis по TTL не создаёт
SessionDestroyedMessage. Истечение локальной сессии и прикладная очистка черезSessionListenerвыполняются Tomcat независимо.
Таймауты
Стандартный таймаут HTTP-сессии задан в WEB-INF/web.xml и равен пяти минутам.
AuthFilter может установить maxInactiveInterval из параметра sessionTimeout для
запросов с токеном. Именно фактический maxInactiveInterval конкретной сессии становится
TTL её ключа в Redis.
sessionTimeout также используется LoginStat для проверки прикладной активности
пользователя. Это отдельный механизм: LoginStat хранится в памяти каждого инстанса и
не становится распределённым при включении CustomSessionManager.
Диагностика
При проверке конфигурации:
-
Убедитесь, что при старте есть сообщение
Initializing custom Session manager (Redis). СообщениеUsing default session managerозначает, что кастомный менеджер выключен. -
Проверьте появление ключа
session:erp:<sessionId>и его TTL после авторизованного запроса. -
В кластере измените сессию на одном инстансе и проверьте, что другой получил сообщение
SessionUpdatedMessageи удалил локальную копию. -
Для завершения сессии проверьте
SessionDestroyedMessageи удаление ключа.
Для ASYNC таймаут записи выводится как Redis timeout on session save или
Redis timeout on session remove; остальные ошибки записываются как Unknown Redis exception.
Ошибки подписки выводятся как Exception on listeners subscribe.
Счётчики BGCRM:type=App (SessionsCount и UsersCount) и экран
Статус сервера читают локальный LoginStat, а не
число ключей в Redis. Поэтому они показывают данные только текущего инстанса.
Основные классы
| Класс | Назначение |
|---|---|
|
Выбирает менеджер при запуске и загружает |
|
Разбирает параметры |
|
Управляет локальным кэшем, загрузкой из store и подписками на уведомления. |
|
Сохраняет сессию после запроса. |
|
Преобразует |
|
Реализуют синхронную и асинхронную запись. |
|
Задаёт операции над Redis, используемые хранилищем, и отделяет их от режима записи. |
|
Описывают уведомления Redis Pub/Sub: источник, идентификатор сессии и тип изменения. |
|
Маркер для значений, которые должны оставаться только в памяти инстанса. |
Модуль покрыт тестами RedissonStoreTest: проверяются загрузка и сохранение атрибутов,
исключение локальных значений, удаление сессии и отписка listeners. Отдельных тестов
жизненного цикла CustomSessionManager, работы SessionStoreValve и обоих режимов
адаптера сейчас нет; изменения в этих частях требуют интеграционной проверки с Redis.