一、谈一下HttpSession
1.HttpSession
其实Session作用域大家做后端开发的都清楚,用于会话级别的作用域,一般我们用其存储常用数据,这样就可以效率更高的拿到常用数据,而不需要再去占用一个数据库连接去查询数据库。
Session是存储在服务端的一个会话级别的对象,对应着客户端的一个会话,Session依赖于浏览器进程,当浏览器进程关闭,其Session也就失效了,因为服务器会根据浏览器的cookie中的JSESSIONID来确定当前客户端的session对象,一般的cookie不去做持久化设置,即内存级别的cookie,所以浏览器一关闭,cookie即被销毁了,对应的JSESSIONID自然无法继续保存。
2.前端的 web 存储技术也有session,application对象(了解)
浏览器的sessionStorage对应着我们后端的session,浏览器的localStorage对应着我们后端的application对象。作用其实都差不多。
3.会出现session一致性问题的场景
我们开发的后端服务都是不记录状态的API接口,也就是不会涉及到视图渲染、数据存储,后端服务接口只负责数据的响应。
自然这些事就需要前端服务来做。
在分布式系统中,各个服务都要为了解决单点故障来做服务器集群。
这时候会当我们的前端服务做了集群之后,必须要做负载均衡,当用户请求过来后第一次被第一个前端服务处理,session数据存储到了第一台服务中,当用户再次访问项目时,被轮询到了第二台服务器,这时候第二台服务器就要重新为其创建一个session,导致用户的数据丢失,以至于功能根本无法正常实现,因为用户从第一台服务器登陆,只有第一台服务器的session中存在用户数据,这就出现了我们关注的数据一致性问题(session一致性问题)。
二、session一致性的几种解决方案
1.session同步(复制) 不使用
如果项目访问人数很少,并且是单体应用架构,可以考虑使用这种方案来解决,即只要有一个用户使用该系统,那么所有服务器上都要创建一个一模一样session对象。
- 优点
- web-server(Tomcat)原生支持,只需要修改配置文件
- 缺点
- session同步需要数据传输,占用大量网络带宽,降低了服务器群的业务处理能力
- 任意一台web-server保存的数据都是所有web-server的session总和,受到内存限制无法水平扩展更多的web-server。
- 大型分布式集群情况下,由于所有web-server都全量保存数据,所以此方案不可取。
2.session存储到cookie中 傻子才用
- 优点
- 服务器不需存储session,用户保存自己的session信息到cookie中。节省服务端资源
- 缺点
- 都是缺点,这只是一种思路。
- 具体如下:
- 每次http请求,携带用户在cookie中的完整信息,浪费网络带宽
- session数据放在cookie中,cookie有长度限制4K,不能保存大量信息
- session数据放在cookie中,存在泄漏、篡改、窃取等安全隐患
- 这种方式不会使用。
3.反向代理 hash一致性
原理:利用反向代理使得同一个用户的请求落在一台固定的服务器上,不要发生服务器切换即可,服务器之前内存session中的数据还是在的;可以有两种实现;
1.四层代理 根据ip做hash一致性
原理:反向代理层使用用户ip来做hash,以保证同一个ip的请求落在同一个web-server(tomcat)上
2.七层代理 根据http协议任意业务字段自定义hash
原理:反向代理使用http协议中的某些业务属性来做hash,例如sid,city_id,user_id等,能够更加灵活的实施hash策略,以保证同一个浏览器用户的请求落在同一个web-server上
OSI七层协议
优点:
只需要改nginx配置,不需要修改应用代码
负载均衡,只要hash属性的值分布是均匀的,多台web-server的负载是均衡的
可以支持web-server水平扩展(session同步法是不行的,受内存限制)
缺点
session还是存在web-server中的,所以web-server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录
如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确的session
但是以上缺点问题也不是很大,因为session本来都是有有效期的。所以这两种反向代理的方式可以使用
4.后端统一存储 session数据
原理:将session存储在web-server后端的存储层,数据库或者缓存;但是由于session是频繁读取的数据而且有过期时间,所以我们一般存在Redis缓存中,而不是MySQL等db中
- 优点:
- 没有安全隐患
- 可以水平扩展,数据库/缓存水平切分即可
- web-server重启或者扩容都不会有session丢失
- 不足
- 增加了一次网络调用,并且需要修改应用代码;如将所有的getSession方法替换为从Redis查数据的方式
总结
- 保证session一致性的架构设计常见方法:
session同步法:多台web-server相互同步数据客户端存储法:一个用户只存储自己的session数据到cookie中- 反向代理hash一致性:
- 保证一个用户的请求落在一台web-server
- 后端统一存储:web-server重启和扩容,session也不会丢失
- 我们最终选择使用“后端统一存储这种方式”
- SpringSession是基于“后端统一存储”这种方式来管理session的;
- 只需配置一个Filter,SpringSession在Filter中使用装饰者模式和适配器模式包装原生request。
- SpringSession可以选择使用JDBC、Redis、Hazelcast、MongoDB等方式存储session
- SpringSession也可以定制请求头中带sessionid或者cookie中带sessionid
- 我们选择使用Redis存储session
三、SpringSession
1.SpringSession简介
SpringSession主要解决分布式情况下,session一致性的问题;
session一致性:只要用户不重启浏览器,每次http短连接请求,理论上服务端都能定位到session,保持会话。
2.为什么要使用SpringSession
1、传统方式session问题
在传统单机web应用中,一般使用tomcat/jetty等web容器时,用户的session都是由容器管理。
浏览器使用cookie中记录sessionid,每次发送请求的时候会带上这个sessionid,web容器根据sessionid找到当时在服务存储信息时使用的那个Map,以此判断用户是否存在会话session。
注意:最大的问题是,session存储在web容器中,被单台服务器容器管理。
在分布式情况下,这会导致什么?
当然,如果我们一直玩单机版的应用,不用关心这个问题,但是随着业务逐渐增大,分布式应用和集群是趋势。
解决session不一致有很多方案,但多配置复杂或者有明显的缺点。有了SpringSession,所有的session由SpringSession创建维护,无需我们修改任何代码,就能在集群环境下使用原生的session方式编程,无侵入、简单配置和Spring应用无缝整合、对接各种session存储方案;
3.SpringSession的简单使用
1.引入依赖
<!-- 引入springboot&redis整合场景 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 引入springboot&springsession整合场景 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2.简单配置redis和spring-session
# redis配置 spring.redis.host=39.98.76.67 #spring.redis.pool.max-active=100 spring.redis.jedis.pool.max-idle=100 # springsession配置 spring.session.store-type=redis
# session过期时间 spring.session.timeout=1800 |
3.controller
@RestController
@SpringBootApplication
public class SpringSession01Application {
public static void main(String[] args) {
SpringApplication.run(SpringSession01Application.class, args);
}
@GetMapping("/set")
public String setSession(HttpSession session){
session.setAttribute("msg", "Hello");
return "ok";
}
@GetMapping("/get")
public String getSession(HttpSession session){
return (String) session.getAttribute("msg");
}
}
我们可以看到,redis中保存了数据,而且如果我们设置多台服务器,访问get获取到的都是一样的 |