http请求是无状态的,用户免登录等问题需要记录用户登录状态(即需要请求有状态),为了解决这个问题引入了 浏览器端的cookie 和 服务器端的session
这里要引入一个概念sessionid,session对象当客户端首次访问时,创建一个新的session对象.并同时生成一个sessionId,并在此次响应中将sessionId以响应报文的方式些回客户端浏览器内存或以重写url方式送回客户端,来保持整个会话
也就是说客户端request请求时候,如果获取了session,就默认分配一个sessionid,然后通过response响应到客户端cookie,然后客户端下一次请求,默认会携带这个sessionid请求到服务端,服务端拿到这个jessionid来区分不同的客户端。
session会话在单台服务器下不会出现共享问题,现在应用部署方式都是分布式,或者集群部署,这样必然会面临一个问题,session无法共享
解决方案:使用公共的session对象存储位置(redis)
nginx提供了ip_hash策略,可以保持用户ip进行hash值计算固定分配到某台服务器上,然后只要是该ip则会保持分配到该服务器上,保证用户访问的是同一台服务器,那么session问题就不存在了。这也是解决session共享的一种方式,也称为黏性session。但是假设一台tomcat服务器挂了的话,那么session也会丢失。所以比较好的方案是抽取session。
tomcat与redis集成实现session共享:
环境为tomcat7 + jdk1.6的话:
在所有需要共享session的服务器的tomcat中目录下:
lib目录中添加以下三个jar包,注意版本最好一致,不然极容易出现错误,下边的测试是可用的:
conf目录中content.xml中加入:配置redis服务
环境为tomcat7 + jdk1.7或1.8的话:
在所有需要共享session的服务器的tomcat中目录下:
lib目录中添加以下三个jar包,测试通过:
conf目录中content.xml中加入:配置redis服务
根据我这测试,是jkd1.8+tomcat7,在137和139两台tomcat中加入jar包且进行如上配置:
修改content.xml
两个注意点
1、按照如上配置,使用redis数据库,放入session中的对象必须要实现java.io.Serializable接口,使用memcache的可以不用实现Serializable接口
原因是:因为tomcat里使用的将session放置redis使用的工具类,是使用的jdk序列化模式存储的,这一点也是很容易理解的,session.setAttribute(String key, Object value),存储Object类型object放入redis中又要能取出来,只能是序列化进行存储了,然后取出的时候进行反序列化。
所以我们在session中存储的任何对象,都必须实现序列化接口。
2、按照如上配置,使用redis做session存储空间时,web应用的session-time的时间单位会变成[秒],而不是原本的[分]
原因是:因为tomcat里使用的将session放置redis使用的工具类,在存储时为对tomcat容器时间做转换在redis中设置过期时间是使用秒作为单位的,有个命令叫expire可以设置redis键值过期时间,所以在context.xml配置文件中我们需要制定session过期时间(默认是60秒,配成1800即30分钟),这一点很重要。
请注意!!!!
context.xml配置说明:
Spring Session 是 Spring 的项目之一。Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案,默认采用外置的 Redis 来存储 Session 数据,以此来解决 Session 共享的 问题。
创建工程结构如下:
在两个子工程session_service1与session_service2中分别添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
分别修改配置文件application.yml:
spring:
# 配置redis
redis:
host: 192.168.0.150
port: 6379
database: 2 # 使用的数据库
# 配置Jedis连接池属性
jedis:
pool:
min-idle: 5 # 连接池中的最小空闲连接
max-idle: 10 # 连接池中的最大空闲连接
max-active: 10 #连接池最大连接数,负值没有限制
max-wait: 2000 # 连接池最大阻塞等待时间(使用负值表示没有限制)
# spring session配置
session:
# 设置 Spring Session 使用 Redis 进行存储。默认配置就是 redis
store-type: redis
# 设置 Spring Session 的过期时间。如果不指定单位模式是 s。
# 也可以通过在启动类上声明@EnableRedisHttpSession进行配置。
# 例如:@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
timeout: 1m # 1分钟
# 缓存到redis相关配置
redis:
# session刷新模式:
# (1)on_save: 保存时刷新,即响应结束后刷新。默认是 on_save
# (2)immediate 实时刷新。
# 也可以通过在启动类上声明@EnableRedisHttpSession进行配置。
# 例如:@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.ON_SAVE)
flush-mode: on_save
# redis存储 Session的命名空间(命名空间主要是用于多个服务之间进行区分)。默认是spring:session
# 也可以通过在启动类上声明@EnableRedisHttpSession进行配置。
# 例如:@EnableRedisHttpSession(redisNamespace=“xxxx”)
namespace: spring:session
session_service1中添加创建session的请求接口
package com.haoqian.session_service1.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/session")
public class SpringSessionTestController {
@RequestMapping("/add/{name}/{value}")
public String addSession(HttpServletRequest request,
@PathVariable("name") String name,
@PathVariable("value") String value) {
HttpSession session = request.getSession();
session.setAttribute(name, value);
return "sessionId:" + session.getId() + " name:" + name;
}
}
session_service1中添加过去session的请求接口
package com.haoqian.session_service2.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/session")
public class SpringSessionTestController {
@RequestMapping("/get/{name}")
public String getSession(HttpServletRequest request,
@PathVariable("name") String name) {
HttpSession session = request.getSession();
String value = (String) session.getAttribute(name);
return "sessionId:" + session.getId() + " value:" + value;
}
}
大功告成!整合完毕,是不是很简单呢
测试:
(0)准备
测试前redis中没有数据
浏览器的cookie中也没有存储sessionId
(1)请求session_service1的添加session接口
从浏览器控制台上可以看到 响应头中返回了加密的 sessionId
(2)请求session_service2的获取session接口
从浏览器控制台上可以看到 请求头中携带了加密的 sessionId
(3)session的存储
在redis中存储了session对象(值得注意的是,redis中缓存的session失效一定时间后会自动移除,不会一致占用内存)
在浏览器cookie中缓存了加密后的sessionId
配置本地域名映射 C:\Windows\System32\drivers\etc\hosts 文件
127.0.0.1 sh.session.com
127.0.0.1 bj.session.com
使用域名访问之前上边的两个接口发现 无法获取session
这是因为浏览器的安全机制,使得session在浏览器端默认不能跨域携带
修改两个spring boot工程的配置文件添加如下配置即可解决问题
但是这样配置后,只有父域名为session.com的才会存储使用相同session
server:
servlet:
session:
cookie:
# 指定Cookie的存放路径为根路径(实现同域名下不同项目的session共享)
# path: /
# 指定Cookie的存放根域名(实现相同根域名,不同子域名的Session共享)
domain: session.com