一、session的处理方式
二、spring session使用的session管理方式
就是Session集中管理的方式:
- 优点:可靠性高,减少Web服务器的资源开销。
- 缺点:实现上有些复杂,配置较多。
- 适用场景:Web服务器较多、要求高可用性的情况。
三、spring session的实现思路
- HttpSession的管理
HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的。
但是把应用搭建成分布式的集群,然后利用F5、LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到多个不同的服务器中
。那问题来了,如何保证不同的服务器能够共享同一份session数据呢?最简单的想法,就是把session数据保存到内存以外的一个统一的地方,例如Memcached/Redis等数据库中。那问题又来了,如何替换掉Servlet容器创建和管理的HttpSession的实现呢?
- 方案一:利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。这方面其实早就有开源项目了,例如
memcached-session-manager(可以参考负载均衡+session共享(memcached-session-manager实现),以及tomcat-redis-session-manager
。不过这种方式有个缺点,就是需要耦合Tomcat/Jetty等Servlet容器的代码。- 方案二:
设计一个Filter,利用HttpServletRequestWrapper,实现自己的getSession()方法,接管创建和管理Session数据的工作
。spring-session就是通过这样的思路实现的。
四、代码例子
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
- config类
@Configuration
public class RedisConfig {
// 将spring session中的序列化器替换成json的,默认的jdk序列化机制太费劲了
@Bean
public GenericJackson2JsonRedisSerializer springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
- entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
Long id;
Integer age;
String position;
String userName;
}
- controller
@RestController
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
/**
* 把用户放入session
* @param request
* @return
*/
@RequestMapping("/user/login")
@ResponseBody
public Map<String, Object> login(HttpServletRequest request) {
// 取出session中的userName
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
logger.info("不存在session,设置user");
User u = new User();
u.setAge(23);
u.setId(1L);
u.setPosition("总裁");
u.setUserName("cristiano ronaldo");
request.getSession().setAttribute("loginUser", u);
} else {
logger.info("存在session,user=" + JSON.toJSONString(loginUser));
}
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
logger.info(cookie.getName() + " : " + cookie.getValue());
}
}
Map<String, Object> resp = new HashMap<String, Object>();
resp.put("code", "000000");
resp.put("msg", "交易成功");
return resp;
}
/**
* 获取session的用户
* @param request
* @return
*/
@RequestMapping("/user/getUser")
@ResponseBody
public Map<String, Object> getUser(HttpServletRequest request) {
Map<String, Object> resp = new HashMap<>();
resp.put("code", "000000");
resp.put("msg", "交易成功");
resp.put("body", request.getSession().getAttribute("loginUser"));
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
logger.info(cookie.getName() + " : " + cookie.getValue());
}
}
return resp;
}
}
- application.yml
server:
port: 8888
spring:
session:
store-type: redis
application:
name: s-session
redis:
host: xx
password: xx
port: 6379
timeout: 10000 # 连接超时时间(毫秒)
database: 3 # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
lettuce:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-idle: 8 # 连接池中的最大空闲连接 默认 8
min-idle: 0 # 连接池中的最小空闲连接 默认 0
- 首次访问 http://127.0.0.1:8888/user/login
再次访问 http://127.0.0.1:8888/user/login
可以看到,redis中已经是以json的形式保存session了