Class ReactiveRedisIndexedSessionRepository
- All Implemented Interfaces:
org.springframework.beans.factory.DisposableBean
,org.springframework.beans.factory.InitializingBean
,ReactiveFindByIndexNameSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
,ReactiveSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
ReactiveSessionRepository
that is implemented using Spring Data's
ReactiveRedisOperations
.
Storage Details
The sections below outline how Redis is updated for each operation. An example of creating a new session can be found below. The subsequent sections describe the details.HMSET spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381 creationTime 1702400400000 maxInactiveInterval 1800 lastAccessedTime 1702400400000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2 EXPIRE spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381 2100 APPEND spring:session:sessions:expires:648377f7-c76f-4f45-b847-c0268bb48381 "" EXPIRE spring:session:sessions:expires:648377f7-c76f-4f45-b847-c0268bb48381 1800 ZADD spring:session:sessions:expirations "1.702402961162E12" "648377f7-c76f-4f45-b847-c0268bb48381" SADD spring:session:sessions:index:PRINCIPAL_NAME_INDEX_NAME:user "648377f7-c76f-4f45-b847-c0268bb48381" SADD spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381:idx "spring:session:sessions:index:PRINCIPAL_NAME_INDEX_NAME:user"
Saving a Session
Each session is stored in Redis as a Hash. Each session is set and updated using the HMSET command. An example of how each session is stored can be seen below.
HMSET spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381 creationTime 1702400400000 maxInactiveInterval 1800 lastAccessedTime 1702400400000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
In this example, the session following statements are true about the session:
- The session id is 648377f7-c76f-4f45-b847-c0268bb48381
- The session was created at 1702400400000 in milliseconds since midnight of 1/1/1970 GMT.
- The session expires in 1800 seconds (30 minutes).
- The session was last accessed at 1702400400000 in milliseconds since midnight of 1/1/1970 GMT.
- The session has two attributes. The first is "attrName" with the value of "someAttrValue". The second session attribute is named "attrName2" with the value of "someAttrValue2".
Optimized Writes
The ReactiveRedisIndexedSessionRepository.RedisSession
keeps track of the
properties that have changed and only updates those. This means if an attribute is
written once and read many times we only need to write that attribute once. For
example, assume the session attribute "attrName2" from earlier was updated. The
following would be executed upon saving:
HMSET spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381 sessionAttr:attrName2 newValue
SessionCreatedEvent
When a session is created an event is sent to Redis with the channel of "spring:session:event:0:created:648377f7-c76f-4f45-b847-c0268bb48381" such that "648377f7-c76f-4f45-b847-c0268bb48381" is the session id. The body of the event will be the session that was created.
SessionDeletedEvent and SessionExpiredEvent
If you configured you Redis server to send keyspace events when keys are expired or deleted, either viaConfigureNotifyKeyspaceEventsReactiveAction
or via external configuration, then deleted and expired sessions will be published as
SessionDeletedEvent
and SessionExpiredEvent
respectively.
Expiration
An expiration is associated to each session using the
EXPIRE command based upon the
ReactiveRedisIndexedSessionRepository.RedisSession.getMaxInactiveInterval()
.
For example:
EXPIRE spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381 2100
You will note that the expiration that is set is 5 minutes after the session actually expires. This is necessary so that the value of the session can be accessed when the session expires. An expiration is set on the session itself five minutes after it actually expires to ensure it is cleaned up, but only after we perform any necessary processing.
NOTE: The findById(String)
method ensures that no expired sessions will
be returned. This means there is no need to check the expiration before using a
session.
Spring Session relies on the expired and delete keyspace notifications from Redis to fire a SessionDestroyedEvent. It is the SessionDestroyedEvent that ensures resources associated with the Session are cleaned up. For example, when using Spring Session's WebSocket support the Redis expired or delete event is what triggers any WebSocket connections associated with the session to be closed.
Expiration is not tracked directly on the session key itself since this would mean the session data would no longer be available. Instead a special session expires key is used. In our example the expires key is:
APPEND spring:session:sessions:expires:648377f7-c76f-4f45-b847-c0268bb48381 "" EXPIRE spring:session:sessions:expires:648377f7-c76f-4f45-b847-c0268bb48381 1800
When a session key is deleted or expires, the keyspace notification triggers a lookup
of the actual session and a SessionDestroyedEvent
is fired.
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if the key has not been accessed. For additional details see How Redis expires keys section in the Redis Expire documentation.
To circumvent the fact that expired events are not guaranteed to happen we can ensure that each key is accessed when it is expected to expire. This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access the key.
For this reason, each session expiration is also tracked by storing the session id in a sorted set ranked by its expiration time. This allows a background task to access the potentially expired sessions to ensure that Redis expired events are fired in a more deterministic fashion. For example:
ZADD spring:session:sessions:expirations "1.702402961162E12" "648377f7-c76f-4f45-b847-c0268bb48381"
NOTE: We do not explicitly delete the keys since in some instances there may be a race condition that incorrectly identifies a key as expired when it is not. Short of using distributed locks (which would kill our performance) there is no way to ensure the consistency of the expiration mapping. By simply accessing the key, we ensure that the key is only removed if the TTL on that key is expired.
Secondary Indexes
By default, Spring Session will also index the sessions by identifying if the session contains any attribute that can be mapped to a principal using anPrincipalNameIndexResolver
. All resolved
indexes for a session are stored in a Redis Set, for example: SADD spring:session:sessions:index:PRINCIPAL_NAME_INDEX_NAME:user "648377f7-c76f-4f45-b847-c0268bb48381" SADD spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381:idx "spring:session:sessions:index:PRINCIPAL_NAME_INDEX_NAME:user"Therefore, you can check all indexes for a given session by getting the members of the
"spring:session:sessions:648377f7-c76f-4f45-b847-c0268bb48381:idx"
Redis set.- Since:
- 3.3
-
Nested Class Summary
-
Field Summary
Modifier and TypeFieldDescriptionstatic final int
The default Redis database used by Spring Session.static final String
The default namespace for each key and channel in Redis used by Spring Session.Fields inherited from interface org.springframework.session.ReactiveFindByIndexNameSessionRepository
PRINCIPAL_NAME_INDEX_NAME
-
Constructor Summary
ConstructorDescriptionReactiveRedisIndexedSessionRepository
(org.springframework.data.redis.core.ReactiveRedisOperations<String, Object> sessionRedisOperations, org.springframework.data.redis.core.ReactiveRedisTemplate<String, String> keyEventsOperations) Creates a new instance with the providedReactiveRedisOperations
. -
Method Summary
Modifier and TypeMethodDescriptionvoid
reactor.core.publisher.Mono<ReactiveRedisIndexedSessionRepository.RedisSession>
Creates a newSession
that is capable of being persisted by thisReactiveSessionRepository
.reactor.core.publisher.Mono<Void>
deleteById
(String id) void
destroy()
void
Disables the clean-up task.reactor.core.publisher.Mono<ReactiveRedisIndexedSessionRepository.RedisSession>
reactor.core.publisher.Mono<Map<String,
ReactiveRedisIndexedSessionRepository.RedisSession>> findByIndexNameAndIndexValue
(String indexName, String indexValue) getSessionCreatedChannel
(String sessionId) reactor.core.publisher.Mono<Void>
Ensures theSession
created byReactiveSessionRepository.createSession()
is saved.void
setCleanupInterval
(Duration cleanupInterval) Sets the interval that the clean-up of expired sessions task should run.void
Sets theClock
to use.void
setDatabase
(int database) Sets the Redis database index used by Spring Session.void
setDefaultMaxInactiveInterval
(Duration defaultMaxInactiveInterval) void
setEventPublisher
(org.springframework.context.ApplicationEventPublisher eventPublisher) void
setIndexResolver
(IndexResolver<Session> indexResolver) void
setRedisKeyNamespace
(String namespace) Sets the namespace for keys used by Spring Session.void
setRedisSessionMapper
(BiFunction<String, Map<String, Object>, reactor.core.publisher.Mono<MapSession>> redisSessionMapper) void
setSaveMode
(SaveMode saveMode) void
setSessionIdGenerator
(SessionIdGenerator sessionIdGenerator) Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Methods inherited from interface org.springframework.session.ReactiveFindByIndexNameSessionRepository
findByPrincipalName
-
Field Details
-
DEFAULT_NAMESPACE
The default namespace for each key and channel in Redis used by Spring Session.- See Also:
-
DEFAULT_DATABASE
public static final int DEFAULT_DATABASEThe default Redis database used by Spring Session.- See Also:
-
-
Constructor Details
-
ReactiveRedisIndexedSessionRepository
public ReactiveRedisIndexedSessionRepository(org.springframework.data.redis.core.ReactiveRedisOperations<String, Object> sessionRedisOperations, org.springframework.data.redis.core.ReactiveRedisTemplate<String, String> keyEventsOperations) Creates a new instance with the providedReactiveRedisOperations
.- Parameters:
sessionRedisOperations
- theReactiveRedisOperations
to use for managing the sessions. Cannot be null.keyEventsOperations
- theReactiveRedisTemplate
to use to subscribe to keyspace events. Cannot be null.
-
-
Method Details
-
afterPropertiesSet
- Specified by:
afterPropertiesSet
in interfaceorg.springframework.beans.factory.InitializingBean
- Throws:
Exception
-
destroy
public void destroy()- Specified by:
destroy
in interfaceorg.springframework.beans.factory.DisposableBean
-
findByIndexNameAndIndexValue
public reactor.core.publisher.Mono<Map<String,ReactiveRedisIndexedSessionRepository.RedisSession>> findByIndexNameAndIndexValue(String indexName, String indexValue) Description copied from interface:ReactiveFindByIndexNameSessionRepository
Find aMap
of the session id to theSession
of all sessions that contain the specified index name index value.- Specified by:
findByIndexNameAndIndexValue
in interfaceReactiveFindByIndexNameSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
- Parameters:
indexName
- the name of the index (i.e.ReactiveFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
)indexValue
- the value of the index to search for.- Returns:
- a
Map
(nevernull
) of the session id to theSession
-
createSession
public reactor.core.publisher.Mono<ReactiveRedisIndexedSessionRepository.RedisSession> createSession()Description copied from interface:ReactiveSessionRepository
Creates a newSession
that is capable of being persisted by thisReactiveSessionRepository
.This allows optimizations and customizations in how the
Session
is persisted. For example, the implementation returned might keep track of the changes ensuring that only the delta needs to be persisted on a save.- Specified by:
createSession
in interfaceReactiveSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
- Returns:
- a new
Session
that is capable of being persisted by thisReactiveSessionRepository
-
save
public reactor.core.publisher.Mono<Void> save(ReactiveRedisIndexedSessionRepository.RedisSession session) Description copied from interface:ReactiveSessionRepository
Ensures theSession
created byReactiveSessionRepository.createSession()
is saved.Some implementations may choose to save as the
Session
is updated by returning aSession
that immediately persists any changes. In this case, this method may not actually do anything.- Specified by:
save
in interfaceReactiveSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
- Parameters:
session
- theSession
to save- Returns:
- indicator of operation completion
-
findById
public reactor.core.publisher.Mono<ReactiveRedisIndexedSessionRepository.RedisSession> findById(String id) Description copied from interface:ReactiveSessionRepository
- Specified by:
findById
in interfaceReactiveSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
- Parameters:
id
- theSession.getId()
to lookup- Returns:
- the
Session
by theSession.getId()
or null if noSession
is found.
-
deleteById
Description copied from interface:ReactiveSessionRepository
- Specified by:
deleteById
in interfaceReactiveSessionRepository<ReactiveRedisIndexedSessionRepository.RedisSession>
- Parameters:
id
- theSession.getId()
to delete- Returns:
- indicator of operation completion
-
setDatabase
public void setDatabase(int database) Sets the Redis database index used by Spring Session.- Parameters:
database
- the database index to use
-
setRedisKeyNamespace
Sets the namespace for keys used by Spring Session. Defaults to 'spring:session:'.- Parameters:
namespace
- the namespace to set
-
setCleanupInterval
Sets the interval that the clean-up of expired sessions task should run. Defaults to 60 seconds. UseDuration.ZERO
to disable it.- Parameters:
cleanupInterval
- the interval to use
-
disableCleanupTask
public void disableCleanupTask()Disables the clean-up task. This is just a shortcut to invokesetCleanupInterval(Duration)
passingDuration.ZERO
-
setClock
Sets theClock
to use. Defaults toClock.systemUTC()
.- Parameters:
clock
- the clock to use
-
setDefaultMaxInactiveInterval
-
setSessionIdGenerator
-
setRedisSessionMapper
public void setRedisSessionMapper(BiFunction<String, Map<String, Object>, reactor.core.publisher.Mono<MapSession>> redisSessionMapper) -
setSaveMode
-
getSessionRedisOperations
-
setEventPublisher
public void setEventPublisher(org.springframework.context.ApplicationEventPublisher eventPublisher) -
setIndexResolver
-
getSessionCreatedChannel
-
getSessionCreatedChannelPrefix
-
getSessionDeletedChannel
-
getSessionExpiredChannel
-