你还不明白如何解决分布式Session?看这篇就够了!

共 8616字,需浏览 18分钟

 ·

2022-06-25 11:26

往期热门文章:

1、40 个 SpringBoot 常用注解:让生产力爆表!

2、3种常见的数据脱敏方案

3、BigDecimal使用不当,造成P0事故!

4、改造BeanUtils,优雅实现List数据拷贝

5、SpringBoot 启动时自动执行代码的几种方式,还有谁不会??


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

图解分析原因:

前提:用户登录功能和图中的商品订单模块、秒杀抢购模块属于单独的微服务模块

用户登录成功后想要访问图中其他两个模块的功能时,由于Nginx使用默认负载均衡策略(轮询),这时请求会按照时间顺序逐一分发到后端应用上,也就是说用户在Tomcat1上登录成功之后,用户的信息放在Tomcat1的Session里,过了一会,用户想要进行秒杀活动的功能操作,请求又被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 依赖,公众号:Java精选-->
<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管理工具
作者:派 大 星.
https://blog.csdn.net/Gaowumao/article/details/124309548

最近热文阅读:

1、40 个 SpringBoot 常用注解:让生产力爆表!
2、3种常见的数据脱敏方案
3、BigDecimal使用不当,造成P0事故!
4、改造BeanUtils,优雅实现List数据拷贝
5、让人上瘾的新一代开发神器,彻底告别Controller、Service、Dao等方法
6、SpringBoot 启动时自动执行代码的几种方式,还有谁不会??
7、延时消息常见实现方案
8、劲爆!Java 通用泛型要来了。。
9、如何写出让同事吐血的代码?
10、遭弃用的 Docker Desktop 放大招!宣布支持 Linux
关注公众号,你想要的Java都在这里

浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报