Spring专区-梳理Spring-Session Redis分布式会话管理的实现

Spring-Session 2.1.x中对spring-session-data-redis中分布式会话设计的一些理解

文章目录

        • Redis存储结构
          • Session信息存储
          • Principal与Session之间的映射
          • 同一时刻过期会话集合
          • 会话有效key
        • 会话事件
          • PUB/SUB
          • Keyspace event notifications
        • RedisSession
        • RedisOperationsSessionRepository
          • deleteById
        • 会话过期策略(RedisSessionExpirationPolicy)
          • onDelete
          • onExpirationUpdated
          • cleanExpiredSessions
        • SessionRepositoryFilter

Redis存储结构

Session信息存储

存储格式:Hash
键值格式:
{keyNameSpace}sessions:{sessionId} ->

Principal与Session之间的映射

存储结构:Set
键值格式:{keyNameSpace}index:{PRINCIPAL_NAME_INDEX_NAME}:{principalName} -> [sessionId0, sessionId1...]

同一时刻过期会话集合

该集合为同一分钟中将要过期的会话集合:
expirationMinute - 1 … .expiredAt … .expirationMinute

存储结构:Set
键值格式:{keyNameSpace}expirations:{expirationMinute} -> [expires:{sessionId0}, expires:{sessionId1}...]

比如:

spring:session:expirations:1586674740000 -> […]
在2020-04-12 14:58:00 ~ 14:59:00 过期的会话列表

该设计值得参考!

会话有效key

理论上,该key存在时,表明session有效;否则,已过期

存储结构:KV
键值格式:{keyNameSpace}sessions:expires:{sessionId} ->""

{keyNameSpace}:index:{}:{sessionId}:{principleName}

会话事件

PUB/SUB

{keyNameSpace}:event:{database}:created:{sessionId}

Keyspace event notifications

__keyevent@{database}__:del

__keyevent@{database}__:expired

RedisSession

关键成员变量:

private final MapSession cached;
private Instant originalLastAccessTime;
private Map delta = new HashMap<>();
private boolean isNew;
private String originalPrincipalName;
private String originalSessionId;

cached–当前访问Session的Java对象表示,用于访问相关属性

originalLastAccessTime–上次访问时间

delta–修改后的属性会在该处缓存

isNew–该Session是否为新创建的Session

originalPrincipalName–principal name

originalSessionId–原Session Id,保存的是一开始初始化时传入的session的id;如果调用了changeSessionId,则该值会在save之后被修改为新生成的sessionId。

RedisSession在更新属性值时,会同时更新delta和cache中的值,如果SaveMode为IMMEDIATE时,RedisSession会在修改属性值之后立即同步至Redis

同步流程:

1、同步sessionId的更新:

 a)判断RedisSession是否修改Session Id(判断originalSessionId和cached.getId()是否相等),如果修改,需要更新相关key:更新会话有效key、更新会话信息key
 b)同步originalSessionId
2、同步属性值:
 a)如果存在principal,需要建立principal的索引
 b)同步delta中的属性值
 c)触发过期策略中的onExpirationUpdated

RedisOperationsSessionRepository

该类为Session管理的核心类,一般跟SessionRepositoryFilter搭配使用。该类实现了对会话信息的创建、存储、删除、查询。

deleteById

根据会话id删除相关会话信息

流程:

1、清除principal索引信息
2、调用过期策略中的onDelete方法
3、删除会话有效Key
4、将maxInactiveInterval设置为0
5、保存会话

会话过期策略(RedisSessionExpirationPolicy)

onDelete

该方法将该被删除的会话id从同一时刻过期会话集合中移除,因为此方法的调用意味着会话的立即删除,而不需要借助cleanExpiredSessions

onExpirationUpdated

1、根据maxInactiveInterval的值采取操作:
 如果maxInactiveInterval < 0,持久化相关会话信息;
 如果maxInactiveInterval = 0,立即删除会话有效key;
 如果maxInactiveInterval > 0,设置会话有效key,过期时间为(lastAccessTime + maxInactiveInterval) seconds;刷新过期会话集合过期时间(lastAccessTime + maxInactiveInterval + 300) seconds;

2、刷新会话信息过期时间为(lastAccessTime + maxInactiveInterval + 300) seconds;

Note:我们注意到,代码中设置会话信息和过期会话集合的过期时间比原过期时间多5min,这是因为需要保证在实际过期之后还能访问到会话信息,我们需要为后面的处理预留一些时间,比如会话过期事件或者会话删除事件的处理。

cleanExpiredSessions

清除上一分钟的过期会话集合中的所有会话信息。该方法被用于定时任务,用于定时清理一些过期的会话,默认cron = 0 * * * * *

SessionRepositoryFilter

 该Filter是设置在所有大部分过滤器之前,将request、reponse进行包装,将request
关于session的操作代理至RedisOperationsSessionRepository类的相关操作上。但是这种方法其实只是实现了会话的共享。但是服务器本身的会话还是存储在本地的(JSESSIONID),这个我觉得就没有太大的必要了。我觉得可以直接借用RedisOperationsSessionRepository的相关方法去实现一个Tomcat的Manager,并使用WebServerCutimizer将其注册至Tomcat中,因此统一会话存储。

@Override
protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
            request, response, this.servletContext);
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
            wrappedRequest, response);

    try {
        filterChain.doFilter(wrappedRequest, wrappedResponse);
    }
    finally {
        wrappedRequest.commitSession();
    }
}

使用装饰者模式进行功能增强,值得借鉴!

你可能感兴趣的:(服务端技术)