public class RedisOperationsSessionRepository extends java.lang.Object implements FindByIndexNameSessionRepository<org.springframework.session.data.redis.RedisOperationsSessionRepository.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
.
JedisConnectionFactory factory = new JedisConnectionFactory(); RedisOperationsSessionRepository redisSessionRepository = new RedisOperationsSessionRepository(factory);
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 sessionAttr2:attrName 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 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
"sessionAttr2" 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 sesion id. The body of the event will be the session that was created.
If registered as a MessageListener
, then
RedisOperationsSessionRepository
will then translate the Redis message into a
SessionCreatedEvent
.
An expiration is associated to each session using the
EXPIRE command based upon the
RedisOperationsSessionRepository.RedisSession.getMaxInactiveIntervalInSeconds()
. 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 getSession(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 they 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 they 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.
PRINCIPAL_NAME_INDEX_NAME
Constructor and Description |
---|
RedisOperationsSessionRepository(org.springframework.data.redis.connection.RedisConnectionFactory redisConnectionFactory)
Allows creating an instance and uses a default
RedisOperations for both
managing the session and the expirations. |
RedisOperationsSessionRepository(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.RedisOperationsSessionRepository.RedisSession |
createSession()
Creates a new
Session that is capable of being persisted by this
SessionRepository . |
void |
delete(java.lang.String sessionId)
|
java.util.Map<java.lang.String,org.springframework.session.data.redis.RedisOperationsSessionRepository.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 session attribute with the name
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME and the value of
the specified principal name. |
org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession |
getSession(java.lang.String id)
|
java.lang.String |
getSessionCreatedChannelPrefix()
Gets the prefix for the channel that SessionCreatedEvent are published to.
|
void |
handleCreated(java.util.Map<java.lang.Object,java.lang.Object> loaded,
java.lang.String channel) |
void |
onMessage(org.springframework.data.redis.connection.Message message,
byte[] pattern) |
void |
save(org.springframework.session.data.redis.RedisOperationsSessionRepository.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 |
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 |
setRedisFlushMode(RedisFlushMode redisFlushMode)
Sets the redis flush mode.
|
void |
setRedisKeyNamespace(java.lang.String namespace) |
public RedisOperationsSessionRepository(org.springframework.data.redis.connection.RedisConnectionFactory redisConnectionFactory)
RedisOperations
for both
managing the session and the expirations.redisConnectionFactory
- the RedisConnectionFactory
to use.public RedisOperationsSessionRepository(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 setDefaultSerializer(org.springframework.data.redis.serializer.RedisSerializer<java.lang.Object> defaultSerializer)
JdkSerializationRedisSerializer
.defaultSerializer
- the new default redis serializerpublic void setRedisFlushMode(RedisFlushMode redisFlushMode)
RedisFlushMode.ON_SAVE
.redisFlushMode
- the new redis flush modepublic void save(org.springframework.session.data.redis.RedisOperationsSessionRepository.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.RedisOperationsSessionRepository.RedisSession>
session
- the Session
to save@Scheduled(cron="${spring.session.cleanup.cron.expression:0 * * * * *}") public void cleanupExpiredSessions()
public org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession getSession(java.lang.String id)
SessionRepository
getSession
in interface SessionRepository<org.springframework.session.data.redis.RedisOperationsSessionRepository.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.RedisOperationsSessionRepository.RedisSession> findByIndexNameAndIndexValue(java.lang.String indexName, java.lang.String indexValue)
FindByIndexNameSessionRepository
Session
of all sessions that contain
the session attribute with the name
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
and the value of
the specified principal name.findByIndexNameAndIndexValue
in interface FindByIndexNameSessionRepository<org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession>
indexName
- the name of the index (i.e.
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
)indexValue
- the value of the index to search for.Session
of all sessions
that contain the session specified index name and the value of the specified index
name. If no results are found, an empty Map is returned.public void delete(java.lang.String sessionId)
SessionRepository
delete
in interface SessionRepository<org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession>
sessionId
- the Session.getId()
to deletepublic org.springframework.session.data.redis.RedisOperationsSessionRepository.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.RedisOperationsSessionRepository.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 handleCreated(java.util.Map<java.lang.Object,java.lang.Object> loaded, java.lang.String channel)
public void setRedisKeyNamespace(java.lang.String namespace)
public java.lang.String getSessionCreatedChannelPrefix()