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

2022年05月13日 阅读数:3
这篇文章主要向大家介绍看完这篇你还不明白如何解决分布式Session?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

日常作的项目都是在一台应用系统,而且全部的操做都在一台Tomcat服务器上,并不会引起Session共享的问题,因此并不会对咱们的系统产生影响,可是当咱们部署多个微服务的时候,再搭配Nginx进行负载均衡时,若是不处理分布式Session问题,咱们在系统中访问不一样功能时就会频繁出现用户登陆的操做 🦋 🦋前端

图解分析缘由:
在这里插入图片描述
前提:用户登陆功能和图中的商品订单模块、秒杀抢购模块属于单独的微服务模块
用户登陆成功后想要访问图中其余两个模块的功能时,因为Nginx使用默认负载均衡策略(轮询),这时请求会按照时间顺序逐一分发到后端应用上,也就是说用户在Tomcat1上登陆成功以后,用户的信息放在Tomcat1Session里,过了一会,用户想要进行秒杀活动的功能操做,请求又被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管理工具

在这里插入图片描述