Сессионное решение в распределенных сценариях — SpringSession

распределенный

Резюме:本文主要研究基于 spring-seesion 解决分布式 session 的共享问题。首先聊一下session与cookie的作用与工作原理,然后步入主题,讲述session 共享问题的产生背景以及常见的解决方案;接着讲述了 spring-session 的两种管理 sessionid 的方式以及对应的使用场景;再接着对后台保存数据到 redis 上的数据结构进行了分析;然后对 spring-session 的核心源代码进行了解读,方便理解 spring-session 框架的实现原理。

Первый: сеанс и файлы cookie

1.Cookie:

Поскольку протокол HTTP не имеет состояния, то есть сервер не знает, что пользователь сделал в прошлый раз, и не может создать ассоциацию одного и того же пользовательского запроса, поэтому браузер должен предоставить серверу механизм идентификации, а затем появляется куки. Информация о состоянии поддерживается за счет внедрения системных механизмов файлов cookie и сеансов. То есть, когда пользователь обращается к серверу в первый раз, в заголовке ответа сервера обычно появляется заголовок ответа Set-Cookie. Здесь файл cookie фактически устанавливается локально. Когда пользователь снова обращается к серверу, http прикрепит этот файл cookie. к прошлому, и файл cookie хранится в файле cookie.Такая информация, как sessionId, передается на сервер, чтобы подтвердить, принадлежит ли он к тому же сеансу.

Атрибуты файлов cookie:

имя: имя файла cookie, после создания файла cookie имя нельзя изменить.

значение: значение файла cookie

комментарий: Описание цели файла cookie. Это описание отображается, когда браузер отображает информацию о файлах cookie.

домен: доменное имя, которое может получить доступ к файлу cookie. Если установлено значение «.baidu.com», все доменные имена, оканчивающиеся на «baidu.com», могут получить доступ к файлу cookie, то есть только доменное имя первого уровня может получить доступ к одному и тому же файлу cookie.

maxAge: время истечения срока действия файла cookie в секундах.

Если это положительное число, оно истечет через maxAge секунд. Если число отрицательное, файл cookie является временным файлом cookie, он становится недействительным после закрытия браузера, и браузер не сохраняет файл cookie ни в какой форме. Если он равен 0, это означает удаление файла cookie. путь: путь использования файла cookie. Например:

path=/, указывающий, что contextPath под этим доменным именем может получить доступ к куки.

path=/app/, только программы, чей contextPath равен «/app», могут получить доступ к куки.

Когда путь задан, он заканчивается на «/».

безопасный: Передается ли файл cookie только с использованием безопасного протокола. Протоколы безопасности здесь включают HTTPS, SSL и т. д. По умолчанию ложно.

Файлы cookie не поддерживают междоменное использование. Для файлов cookie того же происхождения файлы cookie обращают внимание только на доменное имя, игнорируя протокол и порт. Итак, в целом,Файлы cookie для https://localhost:80/ и http://localhost:8080/ являются общими.

Файлы cookie не являются междоменными, доменное имя второго уровня отличается без какой-либо обработки. (wenku.baidu.com и baike.baidu.com). Доступ к одному и тому же файлу cookie возможен только в том случае, если для имени домена установлено значение .baidu.com.

Ограничение количества и размера файлов cookie и политика обработкиwoohoo.cn blog.com/Henry и приложение IE…

2.Session

Механизм Cookie компенсирует отсутствие безгражданства протокола HTTP. До появления Session практически все веб-сайты использовали файлы cookie для отслеживания сеансов. В отличие от файлов cookie, сеансы хранятся на стороне сервера.

Когда клиент запрашивает создание сеанса, сервер сначала проверяет, содержит ли уже запрос клиента идентификатор сеанса — sessionId,

Если этот идентификатор сеанса уже включен, это означает, что сеанс был создан для этого клиента ранее, и сервер извлекает сеанс для использования в соответствии с идентификатором сеанса (если он не может быть получен, он может создать новый) Если запрос клиента не содержит идентификатора сеанса, создайте сеанс для этого клиента и сгенерируйте идентификатор сеанса, связанный с этим сеансом.

Значение sessionId обычно представляет собой строку, которая не повторяется и не поддается имитации. Этот sessionId будет возвращен клиенту в этом ответе для хранения. Способ сохранения sessionId — это в основном cookie.

Расширение: жизненный цикл сеанса

session创建:在第一次使用resquest的getSession方法,web服务器会创建一个session

session使用:session在服务端创建完成后,内存会给session分配一定的空间,并且会生成一个临时cookie返回给用户,浏览器通过set-cookie创建cookie并保存到本地,此后访问都通过此cookieid找到对应的session。

session的销毁:
1.默认销毁:如果与服务端30分钟内没有交互,默认销毁。
2.手动销毁:当调用session的invalidate方法时候会销毁此session。
3.关闭服务器:内存空间被回收了,自然就不存在session了。

Решение — SpringSession

HttpSession создается и управляется контейнером сервлетов, как Tomcat/Jetty хранится в памяти. А если мы встроим веб-сервер в распределенный кластер и будем использовать Nginx для балансировки нагрузки, то Http-запросы от одного и того же пользователя, скорее всего, будут распределяться по двум разным веб-сайтам. Итак, вопрос в том, как гарантировать, что разные веб-сайты могут совместно использовать одни и те же данные сеанса?

Самый простой способ сделать это — вытащить сеанс из контейнера. Во-первых, для достижения этого используйте расширение контейнера.Легче принять, что это реализовано с помощью подключаемых модулей контейнера, таких как tomcat-redis-session-manager на основе Tomcat, jetty-session-redis на основе Jetty и скоро. Преимущество заключается в том, что он прозрачен для проекта и не требует внесения изменений в код. Однако первый в настоящее время не поддерживает Tomcat 8 или не идеален. Однако из-за большой зависимости от контейнеров после обновления или замены контейнера это означает, что все придется начинать сначала. И кода нет в проекте, сопровождение тоже проблема для разработчиков.

Второй — написать набор классов инструментов управления сеансом самостоятельно, включая управление сеансом и управление файлами cookie, которые получаются из вашего собственного класса инструмента, когда вам нужно использовать сеанс, а серверное хранилище класса инструмента может быть размещено в Redis. Очевидно, что это решение является наиболее гибким, но разработка требует дополнительного времени. А Сессионных схем в системе две, легко ошибиться и привести к отсутствию данных.

В-третьих, использовать инструмент управления сеансом фреймворка, который представляет собой Spring-сессию, описанную ниже, Можно понять, что он заменяет управление сеансом сервлета и берет на себя работу по созданию и управлению данными сеанса. Он не зависит от контейнера и не требует изменения кода, а также использует пул соединений spring-data-redis, что можно назвать самым совершенным решением. Конечно, помимо redis management storage, spring-session можно хранить и через базу данных через jdbc

1. Spring Session предоставляет API и реализации для управления информацией о сеансе пользователя. Кроме того, он также предоставляет следующие функции:

2. Разгрузить состояние, сохраненное сеансом, в конкретный внешний агрегат хранилища сеансов, например Redis, который может предоставлять высококачественные кластеры способом, независимым от сервера приложений.

3. Контролируйте, как происходит обмен идентификатором сеанса между клиентом и сервером, чтобы было легко написать Restful API, поскольку он может получить идентификатор сеанса из заголовка HTTP вместо того, чтобы полагаться на файлы cookie.

4. В коде обработки не-веб-запросов можно получить доступ к данным сеанса, например, в коде обработки сообщений JMS.

5. Поддерживает использование нескольких сеансов для каждого браузера, что упрощает создание более богатого опыта для конечных пользователей.

HttpSession может оставаться активным, когда пользователь отправляет запрос с помощью WebSocket.

Вариант 1. Управление идентификатором сеанса с помощью файла cookie (метод управления по умолчанию) Интегрировать springsession в springboot очень просто, представляя

Поскольку spring-session по умолчанию использует стратегию управления файлами cookie, вам нужно всего лишь добавить аннотацию @EnableRedisHttpSession в класс запуска, чтобы использовать spring-session.Параметры могут устанавливать время истечения сеанса, а также местоположение хранилища Redis, обновлять режим и тайминг четкие. maxInactiveIntervalInSeconds — как долго сеанс истечет в секундах.

redisNamespace — позволяет настраивать для сеансов пространства имен для конкретных приложений. Ключи Redis и идентификаторы каналов будут иметь префикс spring:session:: .

redisFlushMode — позволяет указать, когда данные записываются в Redis. По умолчанию используется только при вызове сохранения в SessionRepository. Значение RedisFlushMode.IMMEDIATE будет записано в Redis как можно скорее.

SpringSession предоставляет реализацию интерфейса CookieSerializer по умолчанию, DefaultCookieSerializer.Конечно, в практических приложениях мы также можем сами реализовать этот интерфейс, а затем указать собственный метод реализации с помощью метода CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer).

Вариант 2. Управление идентификатором сеанса через HttpHeader.

spring-session поддерживает заголовки запросов для управления сеансами. Когда файлы cookie отключены, вы можете сопоставить соответствующий сеанс, перенеся токен в заголовок запроса. Spring Session позволяет указать идентификатор сеанса в заголовках для использования RESTful API.

Вот как использовать оба метода управления:Стратегия, связанная с разрешением sessionId в SpringSession, отражается через интерфейс HttpSessionIdResolver. HttpSessionIdResolver имеет два класса реализации:

`public interface HttpSessionIdResolver {

List<String> resolveSessionIds(HttpServletRequest request);
void setSessionId(HttpServletRequest request, HttpServletResponse response,String sessionId);
void expireSession(HttpServletRequest request, HttpServletResponse response);

}

resolveSessionIds: разрешает идентификатор сеанса, связанный с текущим запросом. sessionId может исходить из файлов cookie или заголовков запроса.

setSessionId: отправляет данный идентификатор сеанса клиенту. Этот метод вызывается при создании нового сеанса и сообщает клиенту, что такое новый идентификатор сеанса.

expireSession: указывает клиенту завершить текущий сеанс. Этот метод вызывается, когда сеанс недействителен, и должен уведомить клиента о том, что идентификатор сеанса больше недействителен. Например, он может удалить файл cookie, содержащий sessionId, или установить заголовок ответа HTTP, значение которого пусто, чтобы указать, что клиент больше не отправляет sessionId.

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

Анализ исходного кода SpringSession

Здесь spring-session управляется Redis.Если вам нужно понять, как работает Redis, вам нужно понять класс RedisOperationsSessionRepository.

RedisOperationsSessionRepository — это SessionRepository, реализованный с использованием Spring Data RedisOperations. В веб-среде это обычно сочетается с SessionRepositoryFilter. Эта реализация поддерживает SessionDestroyedEvent и SessionCreatedEvent для SessionMessageListener .

Самый простой API, который Spring Session использует для Session, — это SessionRepository. Этот API преднамеренно очень прост, поэтому легко обеспечить другие реализации базовыми функциями. Некоторые реализации SessionRepository также дополнительно реализуют FindByIndexNameSessionRepository . Например, поддержка Redis в Spring реализует FindByIndexNameSessionRepository. FindByIndexNameSessionRepository добавляет метод для поиска всех сеансов для определенного пользователя. Это делается путем обеспечения того, чтобы атрибут сеанса с именем FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME заполнялся именем пользователя. Ответственность за заполнение этого свойства лежит на разработчике, поскольку Spring Session не знает об используемом механизме аутентификации. Вот пример того, как его использовать:

String username = "username"; this.session.setAttribute( FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);Некоторые реализации FindByIndexNameSessionRepository предоставляют перехватчики для автоматического индексирования других свойств сеанса. Например, многие реализации автоматически гарантируют, что текущее имя пользователя Spring Security индексируется с использованием имени индекса FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME .

String username = "username";

Map<String, Session> sessionIdToSession = this.sessionRepository .findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,username);