/**
* <p>
* A {@link org.springframework.session.SessionRepository} that is implemented using
* Spring Data's {@link org.springframework.data.redis.core.RedisOperations}. In a web
* environment, this is typically used in combination with {@link SessionRepositoryFilter}
* . This implementation supports {@link SessionDeletedEvent} and
* {@link SessionExpiredEvent} by implementing {@link MessageListener}.
* </p>
*
* <h2>Creating a new instance</h2>
*
* A typical example of how to create a new instance can be seen below:
*
* <pre>
* JedisConnectionFactory factory = new JedisConnectionFactory();
*
* RedisOperationsSessionRepository redisSessionRepository = new RedisOperationsSessionRepository(factory);
* </pre>
*
* <p>
* For additional information on how to create a RedisTemplate, refer to the
* <a href = "http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/" >
* Spring Data Redis Reference</a>.
* </p>
*
* <h2>Storage Details</h2>
*
* 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.
*
* <pre>
* 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
* </pre>
*
* <h3>Saving a Session</h3>
*
* <p>
* Each session is stored in Redis as a
* <a href="http://redis.io/topics/data-types#hashes">Hash</a>. Each session is set and
* updated using the <a href="http://redis.io/commands/hmset">HMSET command</a>. An
* example of how each session is stored can be seen below.
* </p>
*
* <pre>
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
* </pre>
*
* <p>
* In this example, the session following statements are true about the session:
* </p>
* <ul>
* <li>The session id is 33fdd1b6-b496-4b33-9f7d-df96679d32fe</li>
* <li>The session was created at 1404360000000 in milliseconds since midnight of 1/1/1970
* GMT.</li>
* <li>The session expires in 1800 seconds (30 minutes).</li>
* <li>The session was last accessed at 1404360000000 in milliseconds since midnight of
* 1/1/1970 GMT.</li>
* <li>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".</li>
* </ul>
*
*
* <h3>Optimized Writes</h3>
*
* <p>
* The {@link 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:
* </p>
*
* <pre>
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
* </pre>
*
* <h3>SessionCreatedEvent</h3>
*
* <p>
* 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.
* </p>
*
* <p>
* If registered as a {@link MessageListener}, then
* {@link RedisOperationsSessionRepository} will then translate the Redis message into a
* {@link SessionCreatedEvent}.
* </p>
*
* <h3>Expiration</h3>
*
* <p>
* An expiration is associated to each session using the
* <a href="http://redis.io/commands/expire">EXPIRE command</a> based upon the
* {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#getMaxInactiveIntervalInSeconds()}
* . For example:
* </p>
*
* <pre>
* EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
* </pre>
*
* <p>
* 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.
* </p>
*
* <p>
* <b>NOTE:</b> The {@link #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
* </p>
*
* <p>
* Spring Session relies on the expired and delete
* <a href="http://redis.io/topics/notifications">keyspace notifications</a> 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.
* </p>
*
* <p>
* 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:
* </p>
*
* <pre>
* APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
* EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
* </pre>
*
* <p>
* When a session expires key is deleted or expires, the keyspace notification triggers a
* lookup of the actual session and a {@link SessionDestroyedEvent} is fired.
* </p>
*
* <p>
* 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
* <a href="http://redis.io/topics/notifications">Timing of expired events</a> section in
* the Redis documentation.
* </p>
*
* <p>
* 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.
* </p>
*
* <p>
* 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:
* </p>
*
* <pre>
* SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
* EXPIRE spring:session:expirations1439245080000 2100
* </pre>
*
* <p>
* 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.
* </p>
* <p>
* <b>NOTE</b>: 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.
* </p>
*
* @author Rob Winch
* @since 1.0
*/
public class RedisOperationsSessionRepository implements
FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
MessageListener {
public void delete(String sessionId) {
RedisSession session = getSession(sessionId, true);
if (session == null) {
return;
}
cleanupPrincipalIndex(session);
this.expirationPolicy.onDelete(session);
String expireKey = getExpiredKey(session.getId());
this.sessionRedisOperations.delete(expireKey);
session.setMaxInactiveIntervalInSeconds(0);
save(session);
}
spring session 3 个 key 意义 以及注意事项
1.真正配置缓解的key,用户监控key缓存失效,触发session失效事件
spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
2.类似hash session相关信息,最近访问时间,到期时间 session.setAttribute
//该SessionRepository.getSession(String)方法确保不会返回到期的会话。有个判断
spring:session:sessions:5b29c067-a4b1-4d51-98b2-be084703fc78 210
3.
set 1497195000000 时间点到期的session key set ,
org.springframework.session.data.redis.RedisOperationsSessionRepository.cleanupExpiredSessions()
定时清理该key,并且访问spring:session:sessions确保ttl任务优先级
spring:session:expirations:1497195000000 210
4.事件:
通过SessionEventHttpSessionListenerAdapter listeners事件redis key 失效删除事件,监控 创建和失效session事件
5.退出登录:
通过session invalidate 方法,失效session退出登录,删除前两个key,并且设置第三个key过期时间
1.真正配置缓解的key,用户监控key缓存失效,触发session失效事件
spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
2.类似hash session相关信息,最近访问时间,到期时间 session.setAttribute
//该SessionRepository.getSession(String)方法确保不会返回到期的会话。有个判断
spring:session:sessions:5b29c067-a4b1-4d51-98b2-be084703fc78 210
3.
set 1497195000000 时间点到期的session key set ,
org.springframework.session.data.redis.RedisOperationsSessionRepository.cleanupExpiredSessions()
定时清理该key,并且访问spring:session:sessions确保ttl任务优先级
spring:session:expirations:1497195000000 210
4.事件:
通过SessionEventHttpSessionListenerAdapter listeners事件redis key 失效删除事件,监控 创建和失效session事件
5.退出登录:
通过session invalidate 方法,失效session退出登录,删除前两个key,并且设置第三个key过期时间