Class RedisIndexedSessionRepository
- All Implemented Interfaces:
org.springframework.beans.factory.DisposableBean
,org.springframework.beans.factory.InitializingBean
,org.springframework.data.redis.connection.MessageListener
,FindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>
,SessionRepository<RedisIndexedSessionRepository.RedisSession>
A SessionRepository
that is implemented using
Spring Data's RedisOperations
. In a web
environment, this is typically used in combination with SessionRepositoryFilter
. This implementation supports SessionDeletedEvent
and
SessionExpiredEvent
by implementing MessageListener
.
Creating a new instance
A typical example of how to create a new instance can be seen below:RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); // ... configure redisTemplate ... RedisIndexedSessionRepository redisSessionRepository = new RedisIndexedSessionRepository(redisTemplate);
For additional information on how to create a RedisTemplate, refer to the Spring Data Redis Reference.
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:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2 EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100 APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe "" EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800 SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe EXPIRE spring:session:expirations:1439245080000 2100
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:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
In this example, the session following statements are true about the session:
- The session id is 33fdd1b6-b496-4b33-9f7d-df96679d32fe
- The session was created at 1404360000000 in milliseconds since midnight of 1/1/1970 GMT.
- The session expires in 1800 seconds (30 minutes).
- The session was last accessed at 1404360000000 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 RedisIndexedSessionRepository.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:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
SessionCreatedEvent
When a session is created an event is sent to Redis with the channel of "spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe" such that "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the session id. The body of the event will be the session that was created.
If registered as a MessageListener
, then RedisIndexedSessionRepository
will then translate the Redis message into a SessionCreatedEvent
.
Expiration
An expiration is associated to each session using the
EXPIRE command based upon the
RedisIndexedSessionRepository.RedisSession.getMaxInactiveInterval()
. For
example:
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 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:33fdd1b6-b496-4b33-9f7d-df96679d32fe "" EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
When a session expires 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. Specifically the background task that Redis uses to clean up expired keys is a low priority task and may not trigger the key expiration. For additional details see Timing of expired events section in the Redis 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 to the nearest minute. 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:
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe EXPIRE spring:session:expirations:1439245080000 2100
The background task will then use these mappings to explicitly request each session expires key. By accessing the key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
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.
- Since:
- 2.2.0
-
Nested Class Summary
Modifier and TypeClassDescriptionfinal class
A custom implementation ofSession
that uses aMapSession
as the basis for its mapping. -
Field Summary
Modifier and TypeFieldDescriptionstatic final String
The default cron expression used for expired session cleanup job.static 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.FindByIndexNameSessionRepository
PRINCIPAL_NAME_INDEX_NAME
-
Constructor Summary
ConstructorDescriptionRedisIndexedSessionRepository
(org.springframework.data.redis.core.RedisOperations<String, Object> sessionRedisOperations) Creates a new instance. -
Method Summary
Modifier and TypeMethodDescriptionvoid
void
Creates a newSession
that is capable of being persisted by thisSessionRepository
.void
deleteById
(String sessionId) void
destroy()
findByIndexNameAndIndexValue
(String indexName, String indexValue) Gets the prefix for the channel thatSessionCreatedEvent
s are published to.Gets the name of the channel thatSessionDeletedEvent
s are published to.Gets the name of the channel thatSessionExpiredEvent
s are published to.Returns theRedisOperations
used for sessions.void
onMessage
(org.springframework.data.redis.connection.Message message, byte[] pattern) void
Ensures theSession
created bySessionRepository.createSession()
is saved.void
setApplicationEventPublisher
(org.springframework.context.ApplicationEventPublisher applicationEventPublisher) Sets theApplicationEventPublisher
that is used to publishSessionDestroyedEvent
.void
setCleanupCron
(String cleanupCron) Set the cleanup cron expression.void
setDatabase
(int database) Sets the database index to use.void
setDefaultMaxInactiveInterval
(int defaultMaxInactiveInterval) Deprecated.void
setDefaultMaxInactiveInterval
(Duration defaultMaxInactiveInterval) Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated.void
setDefaultSerializer
(org.springframework.data.redis.serializer.RedisSerializer<Object> defaultSerializer) Sets the default redis serializer.void
setExpirationStore
(RedisSessionExpirationStore expirationStore) Set theRedisSessionExpirationStore
to use, defaults toRedisIndexedSessionRepository.MinuteBasedRedisSessionExpirationStore
.void
setFlushMode
(FlushMode flushMode) Sets the redis flush mode.void
setIndexResolver
(IndexResolver<Session> indexResolver) Set theIndexResolver
to use.void
setRedisKeyNamespace
(String namespace) void
setRedisSessionMapper
(BiFunction<String, Map<String, Object>, MapSession> redisSessionMapper) void
setSaveMode
(SaveMode saveMode) Set the save mode.void
setSessionIdGenerator
(SessionIdGenerator sessionIdGenerator) Set theSessionIdGenerator
to use to generate session ids.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.FindByIndexNameSessionRepository
findByPrincipalName
-
Field Details
-
DEFAULT_CLEANUP_CRON
The default cron expression used for expired session cleanup job.- See Also:
-
DEFAULT_DATABASE
public static final int DEFAULT_DATABASEThe default Redis database used by Spring Session.- See Also:
-
DEFAULT_NAMESPACE
The default namespace for each key and channel in Redis used by Spring Session.- See Also:
-
-
Constructor Details
-
RedisIndexedSessionRepository
public RedisIndexedSessionRepository(org.springframework.data.redis.core.RedisOperations<String, Object> sessionRedisOperations) Creates a new instance. For an example, refer to the class level javadoc.- Parameters:
sessionRedisOperations
- theRedisOperations
to use for managing the sessions. Cannot be null.
-
-
Method Details
-
afterPropertiesSet
public void afterPropertiesSet()- Specified by:
afterPropertiesSet
in interfaceorg.springframework.beans.factory.InitializingBean
-
destroy
public void destroy()- Specified by:
destroy
in interfaceorg.springframework.beans.factory.DisposableBean
-
setApplicationEventPublisher
public void setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher applicationEventPublisher) Sets theApplicationEventPublisher
that is used to publishSessionDestroyedEvent
. The default is to not publish aSessionDestroyedEvent
.- Parameters:
applicationEventPublisher
- theApplicationEventPublisher
that is used to publishSessionDestroyedEvent
. Cannot be null.
-
setDefaultMaxInactiveInterval
Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated. A negative time indicates that the session will never time out. The default is 30 minutes.- Parameters:
defaultMaxInactiveInterval
- the default maxInactiveInterval
-
setDefaultMaxInactiveInterval
@Deprecated(since="3.0.0") public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) Deprecated.since 3.0.0, in favor ofsetDefaultMaxInactiveInterval(Duration)
Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated. A negative time indicates that the session will never time out. The default is 1800 (30 minutes).- Parameters:
defaultMaxInactiveInterval
- the default maxInactiveInterval in seconds
-
setIndexResolver
Set theIndexResolver
to use.- Parameters:
indexResolver
- the index resolver
-
setDefaultSerializer
public void setDefaultSerializer(org.springframework.data.redis.serializer.RedisSerializer<Object> defaultSerializer) Sets the default redis serializer. Replaces default serializer which is based onJdkSerializationRedisSerializer
.- Parameters:
defaultSerializer
- the new default redis serializer
-
setFlushMode
Sets the redis flush mode. Default flush mode isFlushMode.ON_SAVE
.- Parameters:
flushMode
- the flush mode
-
setSaveMode
Set the save mode.- Parameters:
saveMode
- the save mode
-
setCleanupCron
Set the cleanup cron expression.- Parameters:
cleanupCron
- the cleanup cron expression- Since:
- 3.0.0
- See Also:
-
CronExpression
Scheduled.CRON_DISABLED
-
setDatabase
public void setDatabase(int database) Sets the database index to use. Defaults toDEFAULT_DATABASE
.- Parameters:
database
- the database index to use
-
getSessionRedisOperations
public org.springframework.data.redis.core.RedisOperations<String,Object> getSessionRedisOperations()Returns theRedisOperations
used for sessions.- Returns:
- the
RedisOperations
used for sessions
-
save
Description copied from interface:SessionRepository
Ensures theSession
created bySessionRepository.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 interfaceSessionRepository<RedisIndexedSessionRepository.RedisSession>
- Parameters:
session
- theSession
to save
-
cleanUpExpiredSessions
public void cleanUpExpiredSessions() -
findById
Description copied from interface:SessionRepository
- Specified by:
findById
in interfaceSessionRepository<RedisIndexedSessionRepository.RedisSession>
- Parameters:
id
- theSession.getId()
to lookup- Returns:
- the
Session
by theSession.getId()
or null if noSession
is found.
-
findByIndexNameAndIndexValue
public Map<String,RedisIndexedSessionRepository.RedisSession> findByIndexNameAndIndexValue(String indexName, String indexValue) Description copied from interface:FindByIndexNameSessionRepository
Find aMap
of the session id to theSession
of all sessions that contain the specified index name index value.- Specified by:
findByIndexNameAndIndexValue
in interfaceFindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>
- Parameters:
indexName
- the name of the index (i.e.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
)indexValue
- the value of the index to search for.- Returns:
- a
Map
(nevernull
) of the session id to theSession
of all sessions that contain the specified index name and index value. If no results are found, an emptyMap
is returned.
-
deleteById
Description copied from interface:SessionRepository
- Specified by:
deleteById
in interfaceSessionRepository<RedisIndexedSessionRepository.RedisSession>
- Parameters:
sessionId
- theSession.getId()
to delete
-
createSession
Description copied from interface:SessionRepository
Creates a newSession
that is capable of being persisted by thisSessionRepository
.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 interfaceSessionRepository<RedisIndexedSessionRepository.RedisSession>
- Returns:
- a new
Session
that is capable of being persisted by thisSessionRepository
-
onMessage
public void onMessage(org.springframework.data.redis.connection.Message message, byte[] pattern) - Specified by:
onMessage
in interfaceorg.springframework.data.redis.connection.MessageListener
-
setRedisKeyNamespace
-
setExpirationStore
Set theRedisSessionExpirationStore
to use, defaults toRedisIndexedSessionRepository.MinuteBasedRedisSessionExpirationStore
.- Parameters:
expirationStore
- theRedisSessionExpirationStore
to use, cannot be null- Since:
- 3.4
-
getSessionCreatedChannelPrefix
Gets the prefix for the channel thatSessionCreatedEvent
s are published to. The suffix is the session id of the session that was created.- Returns:
- the prefix for the channel that
SessionCreatedEvent
s are published to
-
getSessionDeletedChannel
Gets the name of the channel thatSessionDeletedEvent
s are published to.- Returns:
- the name for the channel that
SessionDeletedEvent
s are published to
-
getSessionExpiredChannel
Gets the name of the channel thatSessionExpiredEvent
s are published to.- Returns:
- the name for the channel that
SessionExpiredEvent
s are published to
-
setSessionIdGenerator
Set theSessionIdGenerator
to use to generate session ids.- Parameters:
sessionIdGenerator
- theSessionIdGenerator
to use- Since:
- 3.2
-
setRedisSessionMapper
public void setRedisSessionMapper(BiFunction<String, Map<String, Object>, MapSession> redisSessionMapper) - Parameters:
redisSessionMapper
- the mapper to use, cannot be null- Since:
- 3.2
-
setDefaultMaxInactiveInterval(Duration)