目录
session原理
1、用户第一次访问服务器,进行登录;
2、服务器把用户信息保存到session
中,由sessionManager
管理着;可以简单理解session
为map
,为服务器的一片内存空间
3、浏览器保存一个jsessionid=123
的cookie
,这个123
,就相当于是一个key
,通过这个key
,可以从session
获取到用户信息
4、用户以后访问会带上cookie:jsessionid=123
5、浏览器关闭,清除会话cookie
6、下次访问,没有jsessionid
,再创建一个,进入步骤1
Session共享问题
集群环境下的session
共享问题
比如:会员服务部署在多台服务器上,假设浏览器第一次登陆,请求落在了一号服务器,那么一号服务器session
中存了用户信息,那么下一次请求进来,落在了二号服务器,但是二号服务器并没有这个session
,因此又要用户登陆一次,在存一次session
信息
同一服务,集群后,session
不同步问题
分布式环境下的session
共享问题
session
都是有作用域的,不能跨域共享。因此,不能跨不同域名共享。
Session解决方案
方案一:session复制
- 优点
web-server(Tomcat)
原生支持,只需要修改配置 文件
- 缺点
session
同步需要数据传输,占用大量网络带宽,降低了服务器群的业务处理能力- 任意一台
web-server
保存的数据都是所有web- server
的session
总和,受到内存限制无法水平扩展更多的web-server
- 大型分布式集群情况下,由于所有
web-server
都全 量保存数据,所以此方案不可取。 - 假设有
100
台tomcat
,每个tomcat
里面都存了1G
的数据,那么每个tomcat
都得存其他99
个tomcat
的session
数据,99G
! - 小型系统,就
3-5
个tomcat
,使用这种还可以
方案二:客户端存储
- 优点
- 服务器不需存储
session
,用户保存自己的session
信息到cookie
中。节省服务端资源
- 服务器不需存储
- 缺点
- 都是缺点,这只是一种思路。
- 具体如下:
- 每次
http
请求,携带用户在cookie
中的完整信息, 浪费网络带宽 session
数据放在cookie
中,cookie
有长度限制4K
,不能保存大量信息session
数据放在cookie
中,存在泄漏、篡改、 窃取等安全隐患
- 这种方式不会使用。
方案三:hash一致性
利用负载均衡机制,来源于同一个ip的请求,永远用同一个服务器处理或者也可以使用业务字段,比如:用户sid=456,永远使用服务器A处理
-
优点
- 只需要改nginx配置,不需要修改应用代码
- 负载均衡,只要hash属性的值分布是均匀的,多台 web-server的负载是均衡的
- 可以支持web-server水平扩展(session同步法是不行 的,受内存限制)
-
缺点
- session还是存在web-server中的,所以web-server重 启可能导致部分session丢失,影响业务,如部分用户需要重新登录
- 如果web-server水平扩展,rehash后session重新分布, 也会有一部分用户路由不到正确的session
以上缺点问题也不是很大,因为session
本来都是有有效期的。所以这两种反向代理的方式可以使用。
方案四:统一存储
- 优点
- 没有安全隐患
- 可以水平扩展,数据库/缓存水平切分即可
- web-server重启或者扩容都不会有 session丢失
- 不足
- 增加了一次网络调用,并且需要修改应用代码;如将所有的getSession方法替换为从Redis查数据的方式。redis获取数据比内存慢很多
- 上面缺点可以用
SpringSession
完美解决
不同服务,子域session共享
通过方案三或方案四,都能很好的解决同域名下,session
共享的问题。
jsessionid
这个cookie
默认是当前系统域名的。当我们分拆服务,不同域名部署的时候,我们可以使用 如下解决方案;
比如
子域:
auth.gulimall.com
父域是访问不到子域的cookie
的
将子域放大:
父域能访问改cookie
了,有了cookie
,就能到统一存储session
的数据库中获取session
了
不但父域能访问该cookie
,别的子域也能访问
SpringSession
使用SpringSession
解决session
共享问题和子域共享问题。
SpringSession整合Springboot
https://docs.spring.io/spring-session/docs/2.2.3.RELEASE/reference/html5/guides/boot-redis.html
依赖:
<!--spring-session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置:
spring:
redis:
host: 192.168.59.131
port: 6379
##spring session的保存类型
session:
store-type: redis
server:
##session的超时时间 30分钟
servlet:
session:
timeout: 30m
启动类:@EnableRedisHttpSession
测试:
@GetMapping(value = "test/springsession")
@ResponseBody
public void test(HttpSession session){
User user=new User("张三",15);
session.setAttribute("loginUSer",user);
}
@AllArgsConstructor
@Data
class User implements Serializable {
private String userName;
private int age;
}
访问接口,auth.gulimall.com/test/springsession
redis中发现了session
问题:
1.把登录的用户给信息session
存到了redis
中,现在是我想在另一个域名下获取到这个session
2.现在session
中的数据序列化到redis
里,缓存的value
值,默认使用jdk
序列化机制,将序列化后的数据存到redis
,能不能序列化为Json
格式的数据呢?
对于问题1
比如:我想在父域http://gulimall.com/
中获取或者在其他子域中获取,怎么办了?
先在其他域的对应的微服务中,也整合进SpringSession
再说!
比如在父域中整合
<!--spring-session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
spring:
redis:
host: 192.168.59.131
port: 6379
##spring session的保存类型
session:
store-type: redis
@EnableRedisHttpSession
前端:
可以看到父域中也能获取到session
中存储的用户信息!
问题1和2的解决
@Configuration
public class SessionConfig {
/**
* 配置cookie
* @return
*/
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
//放大子域
defaultCookieSerializer.setDomainName("gulimall.com");
defaultCookieSerializer.setCookieName("GULISESSION");
return defaultCookieSerializer;
}
/**
* 修改SpringSession序列化化机制
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer () {
return new GenericJackson2JsonRedisSerializer();
}
}
把这个配置类放到子域名和父域名,对应的微服务中,即可。
启动微服务测试:
登录成功后,自动跳转到首页,查看cookie
,有我们自定义的cookie
信息。
存在redis里的数据也序列化成了json
格式。
这样就解决了session
共享问题!
解决方法来源:
SpringSession原理
@EnableRedisHttpSession导入RedisHttpSessionConfiguration配置
1、给容器中添加了一个组件
SessionRepository =》RedisOperationsSessionRepository=》Redis操作session,session的增删改查封装类
2、SessionRepositoryFilter =>Filter:Session存储过滤器,每个请求过来都必须经过Filter
1、创建的时候,就自动从容器中获取SessionRepository
2、原始的request,response都被包装。SessionRepositoryRequestWrapper,SessionRepositoryResponseWrapper
3、以后获取到session。request.getSession()
4、wrappedRequest.getSession()==>SessionRepository中获取
装饰者模式;
springsesion实现了:
自动延期:只要浏览器不关,session会自动续期
补
session
的运行依赖session id
,而session id
是存在cookie
中的,也就是说,如果浏览器禁用了cookie
,同时session
也会失效(但是可以通过其它方式实现,比如在url
中传递session_id
)- 在
tomcat
中session id
中用JSESSIONID
来表示;
1.在整合SpringSession
之前,登录后,用户信息存在session
里,浏览器上会有一个cookie
,name
是JSESSIONID
,value
是对应的session id
;
2.再整合SpringSession
之后,该cookie
,名字变为SESSION
;
3.在自定义配置了cookie
后,cookie
名字被手动改为了GULISESSION
/**
* 配置cookie
* @return
*/
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
//放大子域
defaultCookieSerializer.setDomainName("gulimall.com");
defaultCookieSerializer.setCookieName("GULISESSION");
return defaultCookieSerializer;
}
但是有一个问题,就是浏览器上看到的sessionId
,始终是和redis
里看到的不一样。
原因:https://blog.csdn.net/wzygis/article/details/103509840
解决:
/**
* 配置cookie
* @return
*/
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
//放大子域
defaultCookieSerializer.setDomainName("gulimall.com");
defaultCookieSerializer.setCookieName("GULISESSION");
defaultCookieSerializer.setUseBase64Encoding(false);
return defaultCookieSerializer;
}
哎,终于解决了我的疑惑,舒服了。