目录
学习地址:
《极客时间-玩转spring全家桶》分布式环境中如何解决Session的问题
常见会话解决方案:
粘性会话 Sticky Session
通过 Load Balance 来实现的,可以让来自同一个用户的会话尽可能的落在同一台机器上面,将分布式的Session变成单机的Session。
缺点:如果服务器下线了原先落在这台服务器的请求就会被分配到别的机器上,原先的会话就会失效了
会话复制 Session Replication
对每一台服务器上的会话都进行一个复制,这样整个集群的所有服务器都有几乎相同的会话信息,不管用户的会话落在哪个服务器上都可以得到它的session。
缺点:因为要对每一台服务器上的会话都进行一个复制,当访问量巨大的时候,需要的成本就变得很大了,每台机器上的Session副本信息也有可能不是一样的。
集中会话 Centralized Session
使用JDBC、Redis来集中存储会话信息,只要有相同的JSESSIONID,就可以把Session从集中会话中取出来,保证了不管是请求落在哪个服务器上都能取到自己想要的Session信息。
SpringSession
支持的存储:Redis,MongoDB,JDBC。SpringSession与这些容器是没有关系的,可以简化集群中的Session管理。
定制 HttpSession
通过封装的 HttpServletRequest 返回定制的 HttpSession。核心类:SessionRepositoryRequestWrapper、SessionRepositoryFilter、DelegatingFilterProxy。
简单看一下SessionRepositoryFilter类
首先是对request、response做了一个封装
然后看SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper怎样获取session的
public SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper.HttpSessionWrapper getSession(boolean create) {
//首先获取一下session
SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper.HttpSessionWrapper currentSession = this.getCurrentSession();
//首先看有没有session
if (currentSession != null) {
return currentSession;
} else {
S requestedSession = this.getRequestedSession();
//如果有拿到requestedSession,进行下面的操作
if (requestedSession != null) {
if (this.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new SessionRepositoryFilter.SessionRepositoryRequestWrapper.HttpSessionWrapper(requestedSession, this.getServletContext());
currentSession.markNotNew();
this.setCurrentSession(currentSession);
return currentSession;
}
} else {
if (SessionRepositoryFilter.SESSION_LOGGER.isDebugEnabled()) {
SessionRepositoryFilter.SESSION_LOGGER.debug("No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
this.setAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
} else if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver && this.response.isCommitted()) {
throw new IllegalStateException("Cannot create a session after the response has been committed");
} else {
if (SessionRepositoryFilter.SESSION_LOGGER.isDebugEnabled()) {
SessionRepositoryFilter.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 " + SessionRepositoryFilter.SESSION_LOGGER_NAME, new RuntimeException("For debugging purposes only (not an error)"));
}
//如果没有拿到session,使用sessionRepository创建
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
//设置在currentSession中
currentSession = new SessionRepositoryFilter.SessionRepositoryRequestWrapper.HttpSessionWrapper(session, this.getServletContext());
this.setCurrentSession(currentSession);
return currentSession;
}
}
}
基于Redis的HttpSession
基本配置
- @EnableRedisHttpSession
- 提供 RedisConnectionFactory
- 实现 AbstractHttpSessionApplicationlnitializer
- 配置 DelegatingFilterProxy
application.properties
//容器设置为Redis
spring.session.store-type=redis
//超时时间
spring.session.timeout=
serverservlet.session.timeout=
spring.session.redis.flush-mode=on-save
spring.session.redis.namespace=spring:session
demo
application.properties:
spring.redis.host=localhost
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
简单test
@SpringBootApplication
@RestController
//开启支持
@EnableRedisHttpSession
public class SessionDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SessionDemoApplication.class, args);
}
@RequestMapping("/hello")
public String printSession(HttpSession session, String name) {
String storedName = (String) session.getAttribute("name");
if (storedName == null) {
session.setAttribute("name", name);
storedName = name;
}
return "hello " + storedName;
}
}
Redis是空的
运行后Redis结果:
因为session是存在了Redis中,只要是相同的JSESSIONID返回还是一样的,所以传参(name=s)或者重启服务,还是会返回(hello high)。