Redis之分布式 Session问题

文章目录

  • [1 分布式 Session](#1 分布式 Session)
    • [1.1 引言](#1.1 引言)
    • [1.2 问题解决方案](#1.2 问题解决方案)
    • [1.3 Java 解决分布式 Session](#1.3 Java 解决分布式 Session)
      • [1.3.1 SpringSession - Redis 解决分布式 Session](#1.3.1 SpringSession - Redis 解决分布式 Session)
        • [1.3.1.1 添加依赖](#1.3.1.1 添加依赖)
        • [1.3.1.2 添加 Redis 配置](#1.3.1.2 添加 Redis 配置)
        • [1.3.1.3 业务逻辑实现](#1.3.1.3 业务逻辑实现)
        • [1.3.1.4 视图控制层](#1.3.1.4 视图控制层)
      • [1.3.2 Redis 解决分布式 Session](#1.3.2 Redis 解决分布式 Session)
        • [1.3.2.1 导入依赖](#1.3.2.1 导入依赖)
        • [1.3.2.2 业务逻辑层](#1.3.2.2 业务逻辑层)
        • [1.3.2.3 视图控制层](#1.3.2.3 视图控制层)

1 分布式 Session

1.1 引言

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

图解分析原因:

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

1.2 问题解决方案

有如下方案:

  • Session 复制:
    优点:无需修改代码,只需要修改 Tomcat 配置
    缺点:
    • Session 同步传输占用内网宽带
    • 多台 Tomcat 同步性能指数级下降
    • Session 占用内存,无法有效水平扩展
  • 前端存储
    优点:不占用服务器内存
    缺点:
    • 存在安全风险
    • 数据大小受 cookie 限制
    • 占用外网宽带
  • Session 粘滞
    优点:无需修改代码,服务端可以水平扩展
    缺点:
    • 增加新机器,会重新 Hash,导致重新登录
    • 应用重新启动后,需要重新登录
  • 后端集中存储
    优点:安全,容易水平扩展
    优点:
    • 增加复杂度
    • 需要修改代码

1.3 Java 解决分布式 Session

1.3.1 SpringSession - Redis 解决分布式 Session

1.3.1.1 添加依赖
xml 复制代码
<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>
1.3.1.2 添加 Redis 配置
yml 复制代码
## 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
1.3.1.3 业务逻辑实现
java 复制代码
/**
 * 登录功能
 */
@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();
}
1.3.1.4 视图控制层
java 复制代码
/**
 * 跳转商品列表
 */
@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 中了

1.3.2 Redis 解决分布式 Session

1.3.2.1 导入依赖
yml 复制代码
<!--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>
1.3.2.2 业务逻辑层
java 复制代码
@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
 */
@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;
}
1.3.2.3 视图控制层
java 复制代码
/**
 * 跳转商品列表
 */
@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 管理工具

相关推荐
guo_wen_qiang1 小时前
mongodb集群模式的原理
服务器·数据库·mongodb
captain3761 小时前
Spring Boot日志
java·数据库·spring boot
阿标在干嘛1 小时前
分布式锁的“场景适配“:我们为什么在部分链路中选择了数据库乐观锁?
数据库·分布式
jiidd1 小时前
关于SQL SERVICE数据库里换行问题
数据库·sql·oracle
java_nnnn1 小时前
数据库读写分离与 Redis 缓存:从 MySQL 查询压力到缓存机制
数据库·redis·缓存
金仓社区的KK1 小时前
数据库主备切换后,业务恢复了吗?五层验收清单
数据库·云计算·软件构建
zoyation1 小时前
HBase 2.5.16 单机部署
大数据·数据库·hbase
l1t1 小时前
DeepSeek总结的 PostgreSQL 中的二进制只读副本,第一部分
数据库·postgresql
java_prinln1 小时前
9. Agent 的长短期记忆系统怎么做的?记忆是怎么存的?粒度是多少?怎么用的?
数据库·python·agent