分布式session-SpringSession的应用
springsession
Spring-Session官方文档:springsession
springsession的特性
Spring Session提供了一套创建和管理Servlet HttpSession的方案,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。
Spring Session提供以下特性:
- API和用于管理用户会话的实现;
- 允许以应用程序容器(即Tomcat)中性的方式替换HttpSession;
- Spring Session 让支持集群会话变得不那么繁琐
- Spring session支持在单个浏览器实例中管理多个用户的会话。
- Spring Session 允许在headers 中提供会话ID以使用RESTful API。
实现原理
Spring-Session的实现就是设计一个过滤器SessionRepositoryFilter , SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和SessionRepositoryResponseWrapper 。后续当第一次调用 request 的getSession方法时,会调用到 SessionRepositoryRequestWrapper 的getSession方法。
如果从request中的属性中查找不到session,再通过cookie拿到sessionid去redis中查找,如果差查不到,就直接创建一个redissession对象,并同步到redis中。将创建销毁session的过程从服务器转移到redis中去。
部分源码
/**
* HttpServletRequest getSession()实现
*/
@Override
public HttpSessionWrapper getSession() {
return getSession(true);
}
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
//从当前请求获取sessionId
String requestedSessionId = getRequestedSessionId();
if (requestedSessionId != null
&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {
S session = getSession(requestedSessionId);
if (session != null) {
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(session, getServletContext());
currentSession.setNew(false);
setCurrentSession(currentSession);
return currentSession;
}
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
}
if (!create) {
return null;
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
+ SESSION_LOGGER_NAME,
new RuntimeException(
"For debugging purposes only (not an error)"));
}
//为当前请求创建session
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
//更新时间
session.setLastAccessedTime(System.currentTimeMillis());
//对Spring session 进行包装(包装成HttpSession)
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
/**
* 根据sessionId获取session
*/
private S getSession(String sessionId) {
S session = SessionRepositoryFilter.this.sessionRepository
.getSession(sessionId);
if (session == null) {
return null;
}
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
/**
* 从当前请求获取sessionId
*/
@Override
public String getRequestedSessionId() {
return SessionRepositoryFilter.this.httpSessionStrategy
.getRequestedSessionId(this);
}
private void setCurrentSession(HttpSessionWrapper currentSession) {
if (currentSession == null) {
removeAttribute(CURRENT_SESSION_ATTR);
}
else {
setAttribute(CURRENT_SESSION_ATTR, currentSession);
}
}
/**
* 获取当前请求session
*/
@SuppressWarnings("unchecked")
private HttpSessionWrapper getCurrentSession() {
return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
}
结合SpringBoot的使用
1. 导入maven依赖
<!--依赖 data-redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--不能忘记这个依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--添加cache的依赖信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--添加 session的依赖-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!--解决spring-session处理缓存时乱码的问题-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.69</version>
</dependency>
2. 编写配置类
我们这里采用使用redis存储session数据实现session共享
spring:
# 配置Redis的使用
redis:
database: 1 # 所使用的数据库 默认是0
host: localhost #所使用的redis的主机地址
port: 7000 # 端口号 默认是 6379
password: # 密码
timeout: 5000 # 超时时间 5000毫秒
# 连接池 lettuce 的配置
lettuce:
pool:
max-active: 100
min-idle: 10
max-wait: 100000
# 配置session的相关信息
session:
store-type: redis # 配置存储的类型
timeout: 3600 # 配置过期时间
redis:
flush-mode: on_save # 保存时刷新
namespace: springSession # 命令空间
server:
port: 8081
servlet:
context-path: /session
3.编写controller进行调用
@RestController
public class SessionController {
@Value("${server.port}")
private String port;
@RequestMapping("/createSession")
public String createSession(HttpSession httpSession){
String sessionId=httpSession.getId();
httpSession.setAttribute("name",port+sessionId);
httpSession.setAttribute("sname",port+":abc");
return sessionId+"创建端口号是:"+port+"的应用创建Session,属性是:"+httpSession.getAttribute("name").toString();
}
@RequestMapping("/getSession")
public String getSession(HttpSession httpSession){
return "访问端口号是:"+port+",获取Session属性是:"+httpSession.getAttribute("name").toString();
}
}
4. 解决session在redis中存储乱码
前面已经导入fastjson,再加上这个配置类
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
// 使用 FastJsonRedisSerializer 来序列化和反序列化redis 的 value的值
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class);
ParserConfig.getGlobalInstance().addAccept("com.muzz");
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setCharset(StandardCharsets.UTF_8);
serializer.setFastJsonConfig(fastJsonConfig);
return serializer;
}
}
5.打包查看效果
使用maven工具中的package 需要启动两个端口来模拟效果,首先启动一个创建session 进入到jar包打包的位置 ,进入cmd命令行
java -jar xxx.jar --server.port=8081
启动另一个端口访问即可 以下是redis存储的session数据
小结
本篇主要介绍SpringSession的特性,结合springboot中的应用,以及多端口实现session共享的访问,下篇我们探究一下SpringSession的设计结构之妙。
转载自:https://juejin.cn/post/7233682328840306725