日常作的项目都是在一台应用系统,而且全部的操做都在一台
Tomcat
服务器上,并不会引起Session共享
的问题,因此并不会对咱们的系统产生影响,可是当咱们部署多个微服务的时候,再搭配Nginx进行负载均衡时,若是不处理分布式Session问题,咱们在系统中访问不一样功能时就会频繁出现用户登陆的操做 🦋 🦋前端
图解分析缘由:
前提:用户登陆功能和图中的商品订单模块、秒杀抢购模块属于单独的微服务模块
用户登陆成功后想要访问图中其余两个模块的功能时,因为Nginx
使用默认负载均衡策略(轮询),这时请求会按照时间顺序逐一分发到后端应用上,也就是说用户在Tomcat1
上登陆成功以后,用户的信息放在Tomcat1
的Session
里,过了一会,用户想要进行秒杀活动的功能操做,请求又被Nginx
分发到了Tomcat2
,而这时的Tomcat2
上的Session
里尚未用户信息,因而就是出现让用户从新登陆的状况,在微服务分布式项目中,不一样的功能模块必然会被分列成各自的微服务,假设访问一个功能都须要从新登陆一次,用户的体验必然会大幅度降低!🤧🤧
java
那如何来解决分布式Session问题呢?
1、解决方案列举
1. Session复制
优势:web
- 无需修改代码,只须要修改Tomcat配置
缺点:redis
- Session同步传输占用内网宽带
- 多台Tomcat同步性能指数级降低
- Session占用内存,没法有效水平扩展
2. 前端存储
优势:spring
- 不占用服务器内存
缺点:数据库
- 存在安全风险
- 数据大小受cookie限制
- 占用外网宽带
3. Session粘滞
优势:apache
- 无需修改代码
- 服务端能够水平扩展
缺点:后端
- 增长新机器,会从新
Hash
,致使从新登陆 - 应用从新启动后,须要从新登陆
4. 后端集中存储
优势:安全
- 安全
- 容易水平扩展
优势:服务器
- 增长复杂度
- 须要修改代码
2、Java代码实现解决分布式Session
1. SpringSession - Redis解决分布式Session
添加依赖
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pools2 对象池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--spring session 依赖-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
添加Redis配置
## Redis配置
spring:
redis:
# 服务器地址
host: localhost
# 端口
port: 6379
# 数据库
database: 0
# 超时时间
connect-timeout: 10000ms
lettuce:
pool:
# 最大链接数
max-active: 8
# 最大链接阻塞等待时间 默认 -1
max-wait: 10000ms
# 最大空闲时间 默认8
max-idle: 200
# 最小空闲链接 默认8
min-idle: 5
业务逻辑实现
/**
* 登陆功能
* @param loginVo
* @return
*/
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
String username = loginVo.getUserName();
String password = loginVo.getPassword();
User user = userMapper.selectByUserName(username);
if (user == null){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//判断密码是否正确
if (!MDUtils.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//使用UUID生成字符串代替Cookie
String ticket = UUIDUtil.uuid();
request.getSession().setAttribute(ticket,user);
CookieUtil.setCookie(request,response,"userTicket",ticket);
return RespBean.success();
}
视图控制层
/**
* 跳转商品列表
* @param session
* @param model
* @return
*/
@RequestMapping("/toList")
public String toList(HttpSession session, Model model,@CookieValue("userTicket")String ticket){
if (StringUtils.isEmpty(ticket)){
return "login";
}
User user = (User) session.getAttribute(ticket);
if (user == null){
return "login";
}
model.addAttribute("user",user);
return "goodsList";
}
登陆测试
打开Redis管理软件发现Session信息已经添加到Redis中了
2. Redis解决分布式Session
导入依赖
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pools2 对象池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
Redis配置参考 【SpringSession - Redis解决分布式Session】
业务逻辑层
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
String username = loginVo.getUserName();
String password = loginVo.getPassword();
User user = userMapper.selectByUserName(username);
if (user == null){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//判断密码是否正确
if (!MDUtils.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//成功Cookie
String ticket = UUIDUtil.uuid();
//将用户信息存入到redis中
redisTemplate.opsForValue().set("userTicket",ticket);
redisTemplate.opsForValue().set("user:"+ticket,user);
//request.getSession().setAttribute(ticket,user);
CookieUtil.setCookie(request,response,"userTicket",ticket);
return RespBean.success();
}
/**
* 根据cookie获取cookie
* @param ticket
* @return
*/
@Override
public User getUserByByCookie(String ticket,HttpServletRequest request,HttpServletResponse response) {
if (StringUtils.isEmpty(ticket)){
return null;
}
User user = (User) redisTemplate.opsForValue().get("user:" + ticket);
if (user == null){
CookieUtil.setCookie(request,response,"userTicket",ticket);
}
return user;
}
视图控制层
/**
* 跳转商品列表
* @param session
* @param model
* @return
*/
@RequestMapping("/toList")
public String toList(HttpSession session, Model model,HttpServletRequest request,HttpServletResponse response){
String ticket = (String) redisTemplate.opsForValue().get("userTicket");
if (StringUtils.isEmpty(ticket)){
return "login";
}
//User user = (User) session.getAttribute(ticket);
User user = userService.getUserByByCookie(ticket, request, response);
if (user == null){
return "login";
}
model.addAttribute("user",user);
return "goodsList";
}
测试成功
查看Redis管理工具