当线上集群时候,会出现session共享问题。

虽然Tomcat提供了session copy的功能,但是缺点比较明显:

1:当Tomcat多的时候,session需要大量同步到多台集群上,占用内网宽带

2:同一个用户session,需要在多个Tomcat中都存在,浪费内存空间.

凯哥自己开发的,领取外卖、打车、咖啡、买菜、各大电商的优惠券的公¥众¥号。如下图:

正文开始:

如果要替换掉Tomcat的session共享,替代方案应该满足:

1:数据共享

2:内存存储

3:key\value结构

基于Redis实现共享session登录

本文由凯哥Java(gz#h:kaigejava),个人blog:www#kaigejava#.com。发布于凯哥Java个人blog

再来回顾下将验证码保存在session中业务流程

我们在session中存放的是:session.setAttribute("code", code); 因为session的特点,每次访问都是一个新的sessionId.我们可以直接使用code作为key.思考:那么如果换成了Redis,还能使用code作为可以吗?

将用户信息存放在session中流程:

用户信息在session中存放:session.setAttribute("user", user); 同样思考:那么如果换成了Redis,还能使用user作为可以吗?

将code和user信息存放在Redis中,流程如下:

验证码数据结构是:string类型的

用户对象数据类型是:hash类型的

根据上面分析,我们修改原来代码:

需要考虑的是:Redis的key规则、过期时间

1:发送验证码的时候,将验证码存放到Redis中时候,需要考虑过期时间。其核心代码如下:

stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);

2:用户登录的时候,将校验验证码以及用户信息存放到Redis中后,返回token

需要考虑的:

1:token不能重复

2:用户过期时间

3:登录成功后,要将token返回给前端

4:用户只要访问,Redis中的过期时间就要延长-在拦截器中处理的

用户登录核心代码修改:

//2.1:校验验证码是否正确
        //String code = (String) session.getAttribute("code");
        String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (StringUtils.isEmpty(code) || !code.equals(loginForm.getCode())) {
            return Result.fail("验证码错误!");
        }
        //2.2:根据手机号查询,如果不存在,创建新用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "phone", "nick_name");
        queryWrapper.eq("phone", phone);
        User user = this.getOne(queryWrapper);
        if (Objects.isNull(user)) {
            log.info("新建用户");
            //新建用户
            user = createUserWithPhone(phone);
        }
        //2.3:保存用户到session中
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setIcon(user.getIcon());
        userDTO.setNickName(user.getNickName());
 
        //session.setAttribute("user", userDTO);
        //2.3.1:获取随机的token,作为用户登录的令牌
        String token = UUID.randomUUID().toString(true);
        //2.3.2:将用户以hash类型存放到Redis中==》将user对象转换成map
        //user对象里有非string类型的字段,用这个方法会报错的
        // Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
        , CopyOptions.create()
        .setIgnoreNullValue(true)
        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
 
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,userMap);
        //LOGIN_USER_TOKEN_TTL
        stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,LOGIN_USER_TOKEN_TTL,TimeUnit.MINUTES);
        //2.3.3: 将token返回
        return Result.ok(token);

需要注意:

在使用stringRedisTemplate存放hash对象的时候,对象中所有的key只能是string类型,如果存在非string类型会报错的。所以这里使用了hootool的BeanUtil工具类:

Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
        , CopyOptions.create()
        .setIgnoreNullValue(true)
        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

拦截器修改代码:

因为拦截器是我们自定义的,所以不能被spring容器管理的,RedisTemplate就不能自动注入了。我们就使用有参构造器,传递:

public class LoginRedisInterceptor implements HandlerInterceptor {
 
    private StringRedisTemplate stringRedisTemplate;
 
    /**
     * 因为这个类不能被spring管理,所以不能直接注入RedisTemplate对象。通过构造函数传递
     * @param stringRedisTemplate
     */
    public LoginRedisInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1:从请求中获取到token
        String token = request.getHeader("authorization");
        if(StringUtils.isEmpty(token)){
            response.setStatus(401);
            return false;
        }
        //2:基于token获取redis中用户对象
        String key = LOGIN_USER_TOKEN_KEY+token;
        Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        //3:判断
        if(userMap.isEmpty()){
            response.setStatus(401);
            return false;
        }
        //将map转对象
        UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        UserHolder.saveUser(user);
        //刷新token的过期时间
        stringRedisTemplate.expire(key,LOGIN_USER_TOKEN_TTL, TimeUnit.MINUTES);
        return true;
    }
 
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

总结:

在使用Redis替换session的时候,需要考虑的问题:

1:选择合适的数据结构

2:选择合适的key

3:选择合适的存储粒度

Redis实战之session共享的更多相关文章

  1. 基于Redis缓存的Session共享(附源码)

    基于Redis缓存的Session共享(附源码) 在上一篇文章中我们研究了Redis的安装及一些基本的缓存操作,今天我们就利用Redis缓存实现一个Session共享,基于.NET平台的Seesion ...

  2. 【Spring Session】和 Redis 结合实现 Session 共享

    [Spring Session]和 Redis 结合实现 Session 共享 参考官方文档 HttpSession with Redis Guide https://docs.spring.io/s ...

  3. .Net Core Web Api实践(三).net core+Redis+docker实现Session共享

    前言:上篇文章介绍了.net core+Redis+IIS+nginx实现Session共享,本来打算直接说明后续填坑过程,但毕竟好多坑是用docker部署后出现的,原计划简单提一下.net core ...

  4. 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案

    分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...

  5. springboot+redis实现分布式session共享

    官方文档,它是spring session项目的redis相关的一个子文档:https://docs.spring.io/spring-session/docs/2.0.0.BUILD-SNAPSHO ...

  6. springboot集成springsession利用redis来实现session共享

    转:https://www.cnblogs.com/mengmeng89012/p/5519698.html 这次带来的是spring boot + redis 实现session共享的教程. 在sp ...

  7. tomcat redis 集群 session共享

    jcoleman/tomcat-redis-session-manager: Redis-backed non-sticky session store for Apache Tomcathttps: ...

  8. Spring Session + Redis实现分布式Session共享

    发表于 2016-09-29 文章目录 1. Maven依赖 2. 配置Filter 3. Spring配置文件 4. 解决Redis云服务Unable to configure Redis to k ...

  9. springboot+spring session+redis+nginx实现session共享和负载均衡

    环境 centos7. jdk1.8.nginx.redis.springboot 1.5.8.RELEASE session共享 添加spring session和redis依赖 <depen ...

  10. SpringBoot2.x+Redis+nginx实现session共享和负载均衡

    1.创建SpringBoot项目添加依赖 <dependency> <groupId>org.springframework.session</groupId> & ...

随机推荐

  1. 手把手教你解决spring boot导入swagger2版本冲突问题,刘老师教编程

    手把手教你解决spring boot导入swagger2版本冲突问题 本文仅为个人理解,欢迎大家批评指错 首先Spring Boot 3 和 Swagger 2 不兼容.在 Spring Boot 3 ...

  2. 树莓派4B-PCA9685驱动舵机

    前言 不知道你们有没有遇到过这么一种情况,直接用树莓派的引脚输出PWM控制舵机,舵机是会出现抖动的.就算代码进行一定的时延迟优化还是会有影响的. 现在我们可以使用PCA9685这个模块去驱动舵机. P ...

  3. node.js 增删改查(原始)

    index.js  连接数据库 const mongoose = require('mongoose') //数据库连接27017是mongodb数据库的默认端口 mongoose.connect(' ...

  4. Java 面向对象编程之InstanceOf关键词和多态

    InstanceOf关键字使用,什么是多态 InstanceOf关键字 是Java的一个二元操作符(运算符),也是Java的保留关键字 语法 //如果该object 是该class的⼀个实例,那⼀个实 ...

  5. yb课堂实战之播放记录表设计和模块开发 《十五》

    play_record表设计 DROP TABLE IF EXISTS `play_record`; CREATE TABLE `play_record` ( `id` int(11) NOT NUL ...

  6. 如何查看Chrome内核版本

    Blink Google chrome即谷歌浏览器原来采用的渲染引擎是Webkit,自chrome 28开始,谷歌浏览器放弃了Webkit,改用自主开发的渲染引擎Blink. 所以现在大多数喜欢尝鲜的 ...

  7. 基于CFX的小型风电机组流场计算流程

    一.Workbench界面框架 二.Geometry模块操作 1.打开Geometry模块,导入txt格式模型 File >> Import External Geometry File ...

  8. Vue查询传参

    通过修改 getWK005 函数来实现这一点.这里的 query 参数就是发送 GET 请求时的查询参数.你可以将需要的条件作为 query 对象的属性传递进去.比如,如果你想要按照特定的条件查询信息 ...

  9. Raid0创建

    实验步骤 步骤1: 确认硬盘 确认你的硬盘设备名. [root@servera ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 ...

  10. oeasy教您玩转python - 012 - # 刷新时间

    ​ 刷新时间 回忆上次内容 通过搜索 我们学会 import 导入 time 了 time 是一个 module import 他可以做和时间相关的事情 time.time() 得到当前时间戳 tim ...