美文网首页读书
你还不明白如何解决分布式Session?看这篇就够了

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

作者: 技术栈 | 来源:发表于2022-06-18 14:46 被阅读0次

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

    图解分析原因:

    image.png

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

    用户登录成功后想要访问图中其他两个模块的功能时,由于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";
    }
    
    

    登录测试

    image.png

    打开Redis管理软件发现Session信息已经添加到Redis中了

    image.png

    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";
    }
    
    

    测试成功

    image.png

    查看Redis管理工具

    image.png

    相关文章

      网友评论

        本文标题:你还不明白如何解决分布式Session?看这篇就够了

        本文链接:https://www.haomeiwen.com/subject/ysgkvrtx.html