⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/Gaowumao/article/details/124309548 「派 大 星.」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

平常做的项目都是在一台应用系统,并且所有的操作都在一台Tomcat服务器上,并不会引发Session共享的问题,所以并不会对我们的系统产生影响,但是当我们部署多个微服务的时候,再搭配Nginx进行负载均衡时,如果不处理分布式Session问题,我们在系统中访问不同功能时就会频繁出现用户登录的操作 🦋 🦋

图解分析原因:

前提:用户登录功能和图中的商品订单模块、秒杀抢购模块属于单独的微服务模块 用户登录成功后想要访问图中其他两个模块的功能时,由于Nginx使用默认负载均衡策略(轮询),这时请求会按照时间顺序逐一分发到后端应用上,也就是说用户在Tomcat1上登录成功之后,用户的信息放在Tomcat1Session里,过了一会,用户想要进行秒杀活动的功能操作,请求又被Nginx分发到了Tomcat2,而这时的Tomcat2上的Session里还没有用户信息,于是就是出现让用户重新登录的情况,在微服务分布式项目中,不同的功能模块必然会被分列成各自的微服务,假设访问一个功能都需要重新登录一次,用户的体验必然会大幅度下降!🤧🤧

那如何来解决分布式Session问题呢?

一、解决方案列举

1. Session复制

优点:

  • 无需修改代码,只需要修改Tomcat配置

缺点:

  • Session同步传输占用内网宽带
  • 多台Tomcat同步性能指数级下降
  • Session占用内存,无法有效水平扩展

2. 前端存储

优点:

  • 不占用服务器内存

缺点:

  • 存在安全风险
  • 数据大小受cookie限制
  • 占用外网宽带

3. Session粘滞

优点:

  • 无需修改代码
  • 服务端可以水平扩展

缺点:

  • 增加新机器,会重新Hash,导致重新登录
  • 应用重新启动后,需要重新登录

4. 后端集中存储

优点:

  • 安全
  • 容易水平扩展

优点:

  • 增加复杂度
  • 需要修改代码

二、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>

业务逻辑层

@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管理工具

文章目录
  1. 1. 那如何来解决分布式Session问题呢?
    1. 1.1. 一、解决方案列举
      1. 1.1.1. 1. Session复制
      2. 1.1.2. 2. 前端存储
      3. 1.1.3. 3. Session粘滞
      4. 1.1.4. 4. 后端集中存储
    2. 1.2. 二、Java代码实现解决分布式Session
      1. 1.2.1. 1. SpringSession - Redis解决分布式Session
      2. 1.2.2. 2. Redis解决分布式Session