Redis解决Session共享问题

这篇具有很好参考价值的文章主要介绍了Redis解决Session共享问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Redis解决Session共享问题,Redis,redis,数据库,缓存

一、集群Session共享问题

session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务器时导致数据丢失的问题
Redis解决Session共享问题,Redis,redis,数据库,缓存
tomcat可以进行多台tomcat进行session拷贝,但是数据拷贝保存相同的内容会存在资源浪费,而且会有时间延迟,所以这种方案不可行

session的替代方案应该满足:

  • 数据共享
  • 内存存储
  • key、value结构

这里我们可以使用redis

二、Redis存储验证码和对象

发送短信:

@Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.校验手机号
        if (phone == null || str.matches("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$")) {
            // 2.如何不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.符合,生成验证码
        String code = RandomUtil.randomNumbers(6);
        // 4.保存验证码到Redis
        stringRedisTemplate.opsForValue().set("login:code:" + phone,code,2, TimeUnit.MINUTES);
        //具体的发送逻辑 在这里就不实现了
        return Result.ok();
    }

首先,我们会校验前端传来的手机号格式,如果格式不正确直接返回。使用hutool的工具类生成6位随机验证码,然后将验证码作为value存入到Redis中,为了避免key重复,我们设置了固定格式的key,并且设置一个2分钟的超时时间,超过两分钟验证码自动失效。

登录功能:

public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1. 校验手机号
        String phone = loginForm.getPhone();
        if (phone == null || str.matches("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$")) {
            // 如何不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 2. 校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get("login:code:" + phone);
        String code = loginForm.getCode();
        // 3. 不一致,报错
        if(cacheCode == null || !cacheCode.equals(code)) {
            return Result.fail("验证码错误!");
        }
        // 4. 一致,根据手机号查询用户 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        // 5. 判断用户是否存在
        if (user == null) {
            // 6. 不存在,创建用户并保存
            user = createUserWithPhone(phone);
        }
        // 7. 保存用户信息到Redis
        String token = UUID.randomUUID().toString(true);
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
        , CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
        stringRedisTemplate.opsForHash().putAll("login:token:" + token,userMap);
        stringRedisTemplate.expire("login:token:" + token,30,TimeUnit.MINUTES);
        return Result.ok(token);
    }

我们在进行登录时,首先会对手机号格式进行检验,如果手机号格式正确,我们从Redis中获取验证码和客户端传来的验证码进行比较,如果一致我们就放行,先去数据库查询该用户信息,如果用户不存在进行保存。
Redis解决Session共享问题,Redis,redis,数据库,缓存
可能有的同学会有疑问,为什么这里要进行这么麻烦的操作呢?
Redis解决Session共享问题,Redis,redis,数据库,缓存
因为我们UserDTO中的id是Long类型的,会报Long转String类型转换异常,因为我们这里使用的是StringRedisTemplate
Redis解决Session共享问题,Redis,redis,数据库,缓存
该类型要求key和value都是String类型,但是我们将对象转为Map时,id为Long类型,所以就出现了该问题,两种方案:1.自定义Map手动put 2.使用BeanUtil,自定义规则

我们需要将用户对象存储在Redis中,这里用什么作为key呢?我们这里用token作为key,将token返回给客户端,客户端后面请求的时候使用该token来获取value。

我们value保存对象时,使用什么存储呢?
1.String:
Redis解决Session共享问题,Redis,redis,数据库,缓存

2.Hash:
Redis解决Session共享问题,Redis,redis,数据库,缓存
我们这里使用Hash存储对象,因为Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且占用内存更少。

我们使用UUID随机生成token,但是我们value是哈希结构,我们使用BeanUtil将对象转为Hash存储,因为Redis是在内存存储的,如果一直只存会存在内存不够用的情况,所以我们这里仍然需要设置一个超时时间,那么设置多长时间呢?我们这里模仿Session的只要超过30分钟不访问就会销毁。
Redis解决Session共享问题,Redis,redis,数据库,缓存
但是我们现在设置的是,从设置开始不管有没有用户访问30分钟后都会销毁,这样肯定是不行的,我们需要和session一样,只要有用户访问我们就需要更新超时时间,那么怎么做呢?可以借助拦截器
Redis解决Session共享问题,Redis,redis,数据库,缓存
我们的拦截器不是Spring创建的对象,所以我们无法使用注入的方式获取StringRedisTemplate对象,我们需要使用构造方法的方法,那么谁来调用呢?
Redis解决Session共享问题,Redis,redis,数据库,缓存
我们可以在MvcConfig注册拦截器时传入StringRedisTemplate对象
由于我们多处都需要用到ThreadLocal存储的对象,所以我们将ThreadLocal封装成一个工具类:

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}
public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(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(StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        // 2. 使用token获取Redis中的对象
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("login:token:" + token);
        // 3. 判断用户是否存在
        if(userMap == null) {
            response.setStatus(401);
            return false;
        }
        // 4. 将Hash 格式转为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 5. 将用户存入ThreadLocal中
        UserHolder.saveUser(userDTO);
        // 6. 刷新token超时时间
        stringRedisTemplate.expire("login:token:" + token,30,TimeUnit.MINUTES);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

大家需要注意的是我们需要remove ThreadLocal,因为ThreadLocal可能会存在内存泄露问题,因为强软引用的问题,这里我们不具体介绍。

三、解决状态登录刷新问题

Redis解决Session共享问题,Redis,redis,数据库,缓存
但是这样会存在一些问题,该拦截器只会拦截需要登录的路径,其他路径是不会拦截了,也就不会进行token有效期的刷新了。怎么解决呢? 新加一个全部路径的拦截器
Redis解决Session共享问题,Redis,redis,数据库,缓存

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(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 (StrUtil.isBlank(token)) {
            return true;
        }
        // 2. 基于token获取Redis中的用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        // 3. 判断用户是否存在
        if(userMap == null) {
            return true;
        }
        // 将查询到的Hash转为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 5. 存在 保存用户到ThreadLocal
        UserHolder.saveUser(userDTO);
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

我们创建一个拦截全部路径的拦截器来进行token有效期的刷新
Redis解决Session共享问题,Redis,redis,数据库,缓存
我们在登录拦截器里,只需要判断ThreadLocal里是否存在有效的用户,如果有放行,否则拦截。

public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                   "/user/code",
                   "/user/login",
                   "/blog/hot",
                   "/shop/**",
                   "/shop-type/**",
                   "/upload/**",
                   "/voucher/**"
                );
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");
    }
}

我们在注册刷新Token的拦截器,并且增加所有路径。
但是我们如何保证刷新Token的拦截器在登录拦截器之前执行呢?其实在MvcConfig中注册拦截器的顺序也就是拦截的顺序,但是这样不保险
Redis解决Session共享问题,Redis,redis,数据库,缓存
其实我们在addInterceptor时会生成一个拦截器注册器对象
Redis解决Session共享问题,Redis,redis,数据库,缓存
拦截器注册器中又有一个order属性,默认都是0,这个值决定拦截器的执行顺序,值越小执行优先级越高。
Redis解决Session共享问题,Redis,redis,数据库,缓存
我们可以通过设置order来决定它们的执行顺序文章来源地址https://www.toymoban.com/news/detail-561728.html

到了这里,关于Redis解决Session共享问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis缓存设计与性能优化【缓存和数据库不一致问题,解决方案:1.加过期时间这样可以一段时间后自动刷新 2.分布式的读写锁】

    在大并发下,同时操作数据库与缓存会存在数据不一致性问题 1、双写不一致情况 2、读写并发不一致 解决方案: 1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致, 可以给缓存数据加上过期时间,每隔一

    2024年04月13日
    浏览(54)
  • Redis 缓存与数据库双写不一致如何解决

    Redis缓存与数据库双写不一致是一个常见的挑战,但可以通过一些方法来解决或减轻这种不一致性。以下是一些可能的解决方案: 事务处理: 在进行缓存和数据库双写时,确保它们被包含在同一事务中。这可以通过使用支持事务的数据库和Redis事务来实现。这样,要么两者同

    2024年01月21日
    浏览(53)
  • session共享(redis实现)

    大厂很多项目都是部署到多台服务器上,这些服务器在各个地区都存在,当我们访问服务时虽然执行的是同一个服务,但是可能是不同服务器运行的; 在我学习项目时遇到这样一个登录情景,假设有如下三台服务器(如图),就使用session存放用户的登录信息,通过该信息可

    2024年02月09日
    浏览(31)
  • 十万字图文详解mysql、redis、kafka、elasticsearch(ES)多源异构不同种类数据库集成、数据共享、数据同步、不同中间件技术实现与方案,如何构建数据仓库、数据湖、数仓一体化?

    数据库大数据量、高并发、高可用解决方案,十万字图文详解mysql、redis、kafka、elasticsearch(ES)多源异构不同种类数据库集成、数据共享、数据同步、不同中间件技术实现与方案,如何构建数据仓库、数据湖、数仓一体化?Delta Lake、Apache Hudi和Apache Iceberg数仓一体化技术架构

    2024年02月07日
    浏览(53)
  • Redis - 做缓存时高并发问题:缓存穿透、击穿、雪崩,数据库缓存双写不一致

    当用户访问的数据既不在缓存也不在数据库中时,就会导致每个用户查询都会“穿透” 缓存“直抵”数据库。这种情况就称为缓存穿透。当高度发的访问请求到达时,缓存穿透不 仅增加了响应时间,而且还会引发对 DBMS 的高并发查询,这种高并发查询很可能会导致 DBMS 的崩

    2024年02月04日
    浏览(48)
  • springboot+shiro+redis实现session共享和cache共享

    在分布式应用中,若是使用了负载均衡,用户第一次访问,连接的A服务器,进行了登录操作进入了系统,当用户再次操作时,请求被转发到了B服务器,用户并没有在B进行登录,此时用户又来到了登录页面,这是难以理解和接受的,这就引出了session共享。 对于shiro安全框架如

    2024年02月11日
    浏览(35)
  • Redis生产实战-热key、大key解决方案、数据库与缓存最终一致性解决方案

    热 key 问题就是某一瞬间可能某条内容特别火爆,大量的请求去访问这个数据,那么这样的 key 就是热 key,往往这样的 key 也是存储在了一个 redis 节点中,对该节点压力很大 那么对于热 key 的处理就是通过热 key 探测系统对热 key 进行计数,一旦发现了热 key,就将热 key 在 jv

    2024年02月05日
    浏览(52)
  • 【Redis】内存数据库Redis进阶(Redis哨兵集群)

    基于 Redis 集群解决单机 Redis 存在的四大问题:   搭建一个三节点形成的 Sentinel 集群,来监管 Redis 主从集群。   【Redis】内存数据库Redis进阶(Redis主从集群)   架构图: 三个sentinel实例信息: 节点 IP PORT s1 192.168.150.101 27001 s2 192.168.150.101 27002 s3 192.168.150.101 27003 之前

    2024年02月14日
    浏览(49)
  • Redis缓存数据库

    目录 一、概述 1、Redis  2、Redis的安装 Redis Windows环境设置 3、String: 字符串 3.1、字符串 3.2、数值 3.3、bitmap 4、Hash: 散列 5、List: 列表 6、Set: 集合 7、Sorted Set: 有序集合 常识: 磁盘:1.寻址:ms(毫秒)2.带宽:MB/s 内存:1.寻址:ns    (纳秒) 2.带宽:GB/s 秒--毫秒--微妙--纳秒

    2024年02月04日
    浏览(62)
  • Redis内存数据库

    Redis内存数据库 NoSQL数据库简介 Redis简介 Redis应用场景 windows下安装和使用Redis 在linux下安装redis Redis数据可视化RedisDesktopManager Redis配置 Redis 数据类型 Redis 字符串(String) Redis 哈希(Hash) Redis 列表(List) Redis 集合(Set) Redis 有序集合(sorted set) Redis key命令 Redis连接命令 Redis服务器命令

    2024年02月09日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包