Session
概念
Session 是客户端与服务器通讯会话技术, 比如浏览器登陆、记录整个浏览会话信息
Session实现原理
客户对向服务器端发送请求后,Session 创建在服务器端,返回Sessionid给客户端浏览器保存在本地,当下次发送请求的时候,在请求头中传递sessionId获取对应的从服务器上获取对应的Sesison
注意Session是保存在服务器端的,浏览器关闭不会到时session失效的。
分布式Session一致性
如果服务器产生了集群后,因为session是存放在服务器上,客户端会使用同一个Sessionid在多个不同的服务器上获取对应的Session,从而会导致Session不一致问题。
演示一致性问题
1.修改hosts文件
127.0.0.1 www.lun.com
2.修改Nginx配置文件,实现负载均衡
###定义上游服务器(需要被nginx真实代理访问的服务器) 默认是轮训机制
upstream backServer{
server 127.0.0.1:8080 ;
server 127.0.0.1:8081 ;
}
server {
listen 80;
server_name www.lun.com;
location / {
### 指定上游服务器负载均衡服务器
proxy_pass http://backServer;
index index.html index.htm;
}
}
3.后端相关代码
实现获取session,建立session操作
@SpringBootApplication
@RestController
public class TestSessionController {
@Value("${server.port}")
private String serverPort;
@RequestMapping("/")
public String index() {
return serverPort;
}
// 创建session 会话
@RequestMapping("/createSession")
public String createSession(HttpServletRequest request, String nameValue) {
// 默认 创建一个session,
HttpSession session = request.getSession();
System.out.println(
"存入Session sessionid:信息" + session.getId() + ",nameValue:" + nameValue + ",serverPort:" + serverPort);
session.setAttribute("name", nameValue);
return "success-" + serverPort;
}
// 获取session 会话
@RequestMapping("/getSession")
public Object getSession(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session == null) {
return serverPort + " 该服务器上没有存放对应的session值";
}
System.out.println("获取Session sessionid:信息" + session.getId() + "serverPort:" + serverPort);
Object value = session.getAttribute("name");
return serverPort + "-" + value;
}
public static void main(String[] args) {
SpringApplication.run(TestSessionController.class, args);
}
}
此时访问www.lun.com就会发现负载均衡轮训效果
建立session,存入session名称
此时已在8081端口号建立了session,但是下一步得到session的时候,会发生问题
8081和8080端口号的服务都没有改session了,这是为什么呢?
在request获取session的时候,可以将参数设置为false,使客户端查不到对应的session的时候,不会直接创建新的session。
// 获取session 会话
@RequestMapping("/getSession")
public Object getSession(HttpServletRequest request) {
// 设置为true 情况下的时候,客户端使用对应的sessionid 查询不到对应的sesison 会直接创建一个新的session
// 设置为false 情况下的时候,客户端使用对应的sessionid 查询不到对应的sesison 不 会直接创建一个新的session
HttpSession session = request.getSession(false);
if (session == null) {
return serverPort + " 该服务器上没有存放对应的session值";
}
System.out.println("获取Session sessionid:信息" + session.getId() + "serverPort:" + serverPort);
Object value = session.getAttribute("name");
return serverPort + "-" + value;
}
这样就能解决sessionId被覆盖的问题。以上就是分布式session一致性问题。
分布式session一致性问题解决方案
- 可以直接使用cookie替代Session(不靠谱)
- 使用Nginx(反向代理)进行ip绑定,同一个ip只能在指定的同一个机器上访问(失去了负载均衡的效果)
- 使用数据库同步session(效率不高)
- tomcat内置支持对session同步(不推荐),同步可能会产生延迟
- 使用Spring-Session框架,相当于把session值缓存到redis中
- 使用token替代session功能,token最终存放在redis中,redis支持分布式共享(最靠谱)
使用Spring-Session框架
1、pom文件引入
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> </exclusion> </exclusions> -->
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- Testing Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring session 与redis应用基本环境配置,需要开启redis后才可以使用,不然启动Spring boot会报错 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
2、配置文件
server:
port: 8081
spring:
redis:
database: 0
host: 192.168.212.151
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
redis:
hostname: 192.168.212.151
port: 6379
password: 123456
3、后端相关代码
1.建立sessonConfig类,用于配置redis服务器的连接
//这个类用配置redis服务器的连接
//maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
// 冒号后的值为没有配置文件时,制动装载的默认值
@Value("${redis.hostname:localhost}")
String hostName;
@Value("${redis.port:6379}")
int port;
@Value("${redis.password}")
String password;
@Bean
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory connection = new JedisConnectionFactory();
connection.setPort(port);
connection.setHostName(hostName);
connection.setPassword(password);
return connection;
}
}
2.初始化session配置
//初始化Session配置
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer {
public SessionInitializer() {
super(SessionConfig.class);
}
}
这样就可以解决分布式session一致性的问题。简单来说,底层就是重写HTTPSession类,将我们的session值存放到redis中。