1)HttpSession是通过Servlet容器进行创建和管理的。在单服务环境中,通过Http请求创建的Session信息是存储在Web服务器内存中的,如Tomcat、Jetty等。
2)现在很多的服务器都采用分布式集群的方式进行部署,用户在发起第一次请求时候访问了A站点,并在A站点的session中保存了登录信息,
当用户第二次发起请求,通过负载均衡请求分配到B站点了,那么此时B站点能否获取用户保存的登录的信息呢?答案是不能的,因为Session是存储在对应Web服务器的内存中的,不能进行共享,可以用Spring-sessio解决session的共享问题。
在分布式结构的系统中,每台服务器之间有着不可共享数据的鸿沟,比如用户登录信息或其他一次请求需要在多个服务器间随机使用的情况下,可使用session共享
当所有的tomcat(即多个服务器)都往session中写数据时,就统一往redis中写数据,同理所有tomcat读数据就去redis中读,这样就可以做到session共享了,不同的服务就可以使用相同的 Session 数据了,这样的方案,可以由开发者手动实现,即手动往 Redis 中存储数据,手动从 Redis 中读取数据,相当于使用一些 Redis 客户端工具来实现这样的功能,毫无疑问,手动实现工作量还是蛮大的。
一个简化的方案就是使用 Spring Session 来实现这一功能,
Spring Session 就是使用 Spring 中的代理过滤器,将所有的 Session 操作拦截下来,自动的将数据 同步到 Redis 中,或者自动的从 Redis 中读取数据。
对于开发者来说,所有关于 Session 同步的操作都是透明的,开发者使用 Spring Session,一旦配置完成后,具体的用法就像使用一个普通的 Session 一样。
如果每改一个session的属性就触发存储,在变更较多session属性时会触发多次redis写操作,对性能也会有影响,我们是在每次请求处理完后,做一次session的写入,并且之写入变更过的属性。
如果本次没有做session的更改, 是不会做redis写入的,仅当没有变更的session超过一个时间阀值(不变更session刷新过期时间的阀值),就会触发session保存,以便session能够延长有效期
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-web
org.springframework.session
spring-session-data-redis
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-core
5.1.6.RELEASE
#Tomcat端口 不同服务的端口不同
server:
port: 8080
#Redis
#Redis数据库索引(默认为0)
redis:
#数据库名
database: 0
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
lettuce:
pool:
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
因为Redis作为session数据保存的中间件,所以需要先配置redis,更多的rdis操作可参考我的另一篇博客 https://blog.csdn.net/weixin_43784880/article/details/104321420
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
//maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}
maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
@EnableCaching开启redis
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.UUID;
@RestController
@RequestMapping("/test")
public class TestController {
@Value("${server.port}")
Integer port;
@RequestMapping(value = "/set", method = RequestMethod.POST)
public String set(HttpSession session) {
session.setAttribute("session2", "guangzhou");
return String.valueOf(port) + session.getId();
}
@RequestMapping(value = "/get", method = RequestMethod.GET)
public String get(HttpSession session) {
return session.getAttribute("session2") + ":" + port;
}
}
为了获取每一个请求到底是哪一个 服务器 提供的服务(负载均衡随机分配),需要在每次请求时返回当前服务的端口号,因此这里我注入了 server.port
此处您可以为了方便测试,如果没有现成的分布式架构的项目,可按照上方步骤配置两个一模一样的springboot项目
除了properties.yml的server.port 即tomcat的服务端口不同之外,其他几乎一致,比如配置两个项目,分别为server.port=8080、server.port=8081
分别测试web类中get和set请求
注:测试前要先启动redis 如何启动自行百度
工具postman或浏览器实现 Post请求 http://127.0.0.1:8080/test/set
返回值为请求到的服务器端口+sessionid
Get请求 http://127.0.0.1:8081/test/get
返回值为set方法保存的session的value值
通过命令在redis根目录打开客户端查看session的保存情况 redis.cli -p port 此处我的是默认端口6379
使用keys ‘session’ 查看redis中所有的key带session数据 此处*表示模糊查询 查处的的数据是key带有session字眼的数据
也可以使用keys * 查出所有的缓存数据 为了避免结果返回过多难以查阅 所以使用keys ‘session查找
keys ‘sesion’
keys *
查出来后可以看到红圈处的内容与我们get查找出来的内容一致,说明我们session不仅可以在多个服务器间共享,同时可以再redis中看到
使用spring session+redis存储的session如何查看.
127.0.0.1:6379> keys '*session*'
1) "spring:session:sessions:ba9c52ac-96a1-4252-ab6e-c2a2f489419f"
2) "spring:session:sessions:expires:ba9c52ac-96a1-4252-ab6e-c2a2f489419f"
3) "spring:session:expirations:1584436920000"
4) "spring:session:expirations:1584341460000"
5) "spring:session:sessions:16829b83-860f-4841-8943-8ce6d3d8886e"
6) "spring:session:sessions:fce83f11-1012-4bbf-866e-d0693a81bc37"
7) "spring:session:sessions:expires:fce83f11-1012-4bbf-866e-d0693a81bc37"
8) "spring:session:sessions:expires:16829b83-860f-4841-8943-8ce6d3d8886e"
9) "spring:session:expirations:1584286860000"
127.0.0.1:6379> type spring:session:sessions:fce83f11-1012-4bbf-866e-d0693a81bc37
hash
127.0.0.1:6379> hkeys spring:session:sessions:fce83f11-1012-4bbf-866e-d0693a81bc37
1) "sessionAttr:session1"
2) "sessionAttr:uid"
3) "sessionAttr:session2"
4) "creationTime"
5) "maxInactiveInterval"
6) "lastAccessedTime"
上图即我们java存储的session数据 session1和uid 是我之前测试的 不必理睬
存储在redis中的key的简单介绍说明:
type 查看数据类型
//存储 Session 数据,数据类型hash,可以使用type查看
Key:spring:session:sessions:eeefefeae-c8ea-4346-ba6b-9b3b26eee578
使用
type spring:session:sessions:eeefefeae-c8ea-4346-ba6b-9b3b26eee578 返回的是hash 也是我们session保存的位置,session实质就是key-value
eeefefeae-c8ea-4346-ba6b-9b3b26eee578 为 sessionId
//Redis TTL触发Session 过期。(Redis 本身功能),数据类型:String
Key:spring:session:sessions:expires:133337740000
使用
type spring:session:sessions:expires:133337740000 返回时String 表示的是该session缓存的有效时间
//执行 TTL key ,可以查看剩余生存时间
//定时Job程序触发Session 过期。(spring-session 功能),数据类型:Set
Key:spring:session:expirations:133337740000
使用
type spring:session:expirations:133337740000 返回的是set集合的数据类型