public class RedisIndexedSessionRepository extends java.lang.Object implements FindByIndexNameSessionRepository<org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession>, org.springframework.data.redis.connection.MessageListener
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
.
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.
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:expirations1439245080000 2100
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 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
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
.
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:expirations1439245080000 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.
Modifier and Type | Field and Description |
---|---|
static int |
DEFAULT_DATABASE
The default Redis database used by Spring Session.
|
static java.lang.String |
DEFAULT_NAMESPACE
The default namespace for each key and channel in Redis used by Spring Session.
|
PRINCIPAL_NAME_INDEX_NAME
Constructor and Description |
---|
RedisIndexedSessionRepository(org.springframework.data.redis.core.RedisOperations<java.lang.Object,java.lang.Object> sessionRedisOperations)
Creates a new instance.
|
Modifier and Type | Method and Description |
---|---|
void |
cleanupExpiredSessions() |
org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession |
createSession()
Creates a new
Session that is capable of being persisted by this
SessionRepository . |
void |
deleteById(java.lang.String sessionId)
|
org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession |
findById(java.lang.String id)
|
java.util.Map<java.lang.String,org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession> |
findByIndexNameAndIndexValue(java.lang.String indexName,
java.lang.String indexValue)
Find a
Map of the session id to the Session of all sessions that
contain the specified index name index value. |
java.lang.String |
getSessionCreatedChannelPrefix()
Gets the prefix for the channel that
SessionCreatedEvent s are published to. |
java.lang.String |
getSessionDeletedChannel()
Gets the name of the channel that
SessionDeletedEvent s are published to. |
java.lang.String |
getSessionExpiredChannel()
Gets the name of the channel that
SessionExpiredEvent s are published to. |
org.springframework.data.redis.core.RedisOperations<java.lang.Object,java.lang.Object> |
getSessionRedisOperations()
Returns the
RedisOperations used for sessions. |
void |
onMessage(org.springframework.data.redis.connection.Message message,
byte[] pattern) |
void |
save(org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession session)
Ensures the
Session created by
SessionRepository.createSession() is saved. |
void |
setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher applicationEventPublisher)
Sets the
ApplicationEventPublisher that is used to publish
SessionDestroyedEvent . |
void |
setDatabase(int database)
Sets the database index to use.
|
void |
setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval)
Sets the maximum inactive interval in seconds between requests before newly created
sessions will be invalidated.
|
void |
setDefaultSerializer(org.springframework.data.redis.serializer.RedisSerializer<java.lang.Object> defaultSerializer)
Sets the default redis serializer.
|
void |
setFlushMode(FlushMode flushMode)
Sets the redis flush mode.
|
void |
setIndexResolver(IndexResolver<Session> indexResolver)
Set the
IndexResolver to use. |
void |
setRedisKeyNamespace(java.lang.String namespace) |
void |
setSaveMode(SaveMode saveMode)
Set the save mode.
|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
findByPrincipalName
public static final int DEFAULT_DATABASE
public static final java.lang.String DEFAULT_NAMESPACE
public RedisIndexedSessionRepository(org.springframework.data.redis.core.RedisOperations<java.lang.Object,java.lang.Object> sessionRedisOperations)
sessionRedisOperations
- the RedisOperations
to use for managing the
sessions. Cannot be null.public void setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher applicationEventPublisher)
ApplicationEventPublisher
that is used to publish
SessionDestroyedEvent
. The default is to not publish a
SessionDestroyedEvent
.applicationEventPublisher
- the ApplicationEventPublisher
that is used
to publish SessionDestroyedEvent
. Cannot be null.public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval)
defaultMaxInactiveInterval
- the number of seconds that the Session
should be kept alive between client requests.public void setIndexResolver(IndexResolver<Session> indexResolver)
IndexResolver
to use.indexResolver
- the index resolverpublic void setDefaultSerializer(org.springframework.data.redis.serializer.RedisSerializer<java.lang.Object> defaultSerializer)
JdkSerializationRedisSerializer
.defaultSerializer
- the new default redis serializerpublic void setFlushMode(FlushMode flushMode)
FlushMode.ON_SAVE
.flushMode
- the flush modepublic void setSaveMode(SaveMode saveMode)
saveMode
- the save modepublic void setDatabase(int database)
DEFAULT_DATABASE
.database
- the database index to usepublic org.springframework.data.redis.core.RedisOperations<java.lang.Object,java.lang.Object> getSessionRedisOperations()
RedisOperations
used for sessions.RedisOperations
used for sessionspublic void save(org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession session)
SessionRepository
Session
created by
SessionRepository.createSession()
is saved.
Some implementations may choose to save as the Session
is updated by
returning a Session
that immediately persists any changes. In this case,
this method may not actually do anything.
save
in interface SessionRepository<org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession>
session
- the Session
to savepublic void cleanupExpiredSessions()
public org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession findById(java.lang.String id)
SessionRepository
findById
in interface SessionRepository<org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession>
id
- the Session.getId()
to lookupSession
by the Session.getId()
or null if no
Session
is found.public java.util.Map<java.lang.String,org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession> findByIndexNameAndIndexValue(java.lang.String indexName, java.lang.String indexValue)
FindByIndexNameSessionRepository
Map
of the session id to the Session
of all sessions that
contain the specified index name index value.findByIndexNameAndIndexValue
in interface FindByIndexNameSessionRepository<org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession>
indexName
- the name of the index (i.e.
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
)indexValue
- the value of the index to search for.Map
(never null
) of the session id to the Session
of all sessions that contain the specified index name and index value. If no
results are found, an empty Map
is returned.public void deleteById(java.lang.String sessionId)
SessionRepository
deleteById
in interface SessionRepository<org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession>
sessionId
- the Session.getId()
to deletepublic org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession createSession()
SessionRepository
Session
that is capable of being persisted by this
SessionRepository
.
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.
createSession
in interface SessionRepository<org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession>
Session
that is capable of being persisted by this
SessionRepository
public void onMessage(org.springframework.data.redis.connection.Message message, byte[] pattern)
onMessage
in interface org.springframework.data.redis.connection.MessageListener
public void setRedisKeyNamespace(java.lang.String namespace)
public java.lang.String getSessionCreatedChannelPrefix()
SessionCreatedEvent
s are published to.
The suffix is the session id of the session that was created.SessionCreatedEvent
s are published
topublic java.lang.String getSessionDeletedChannel()
SessionDeletedEvent
s are published to.SessionDeletedEvent
s are published topublic java.lang.String getSessionExpiredChannel()
SessionExpiredEvent
s are published to.SessionExpiredEvent
s are published to