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

往期热门文章:

1、40 个 SpringBoot 常用注解:让生产力爆表!
2、3种常见的数据脱敏方案
3、BigDecimal使用不当,造成P0事故!
4、改造BeanUtils,优雅实现List数据拷贝
5、SpringBoot 启动时自动执行代码的几种方式,还有谁不会??

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

图解分析原因:

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

登录测试

f03e861a5cb025a5585674504d17257b.png

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

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

测试成功

ae9b11f457c2cb57718bc0c60be1866e.png

查看Redis管理工具

b89706aaaf01e3e0568d3a2f04829454.png

作者:派 大 星.

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

842a3efc84c60e88d0a2324e55da099d.png

关注公众号,你想要的Java都在这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值