Redis实际应用1

前言

该项目是黑马得免费项目 请官网领取 点我免费获取

基于Session登录

业务逻辑:
在这里插入图片描述

学到的点:

ThreadLocal

在每一个线程里面 进行独立的map存放数据信息 ,对于一些需要存放在本地的变量 ,以防多线程出现的安全隐患顺便方便同一个线程多次访问所需要的数据。
在这里插入图片描述
由于是弱引用 所以在使用完以后 我们应该记得进行清理:
下图是在进行拦截的时候的操作在这里插入图片描述

糊涂工具包的使用

很多有用的简便方法值得使用

  <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>

集群Session问题

发生的原因:
多台tomcat之间 不能共享Session数据,当进行切换时则会发生问题。
解决方案:
用redis 因为Session 里面放的也是 key -value Redsi刚好满足

流程:

短信验证码的流程修改:

在这里插入图片描述

思路

原本是将验证码存放在 session中存取验证,但是由于共享的问题转为Redis进行存放。
现在:采用String,String的形式存放,并且以手机号作为关键的Key实现唯一性。

User对象存放的流程修改:

在这里插入图片描述

思路

原来的思路: 将对象存放到session之中,服务器会给每一个session一个id,存放到用户的浏览器的cookie之中,下次根据这个sessionId,获取到用户的信息,以达到免登录的效果,但是还是由于集群的问题,此方案废弃
现在的思路: 在存放对象时,生产随机的token当作Key,随后传给前端,前端在将token写入,每次请求的时候都带着这个token。存放的方式 采用 hash进行存放方便后续的修改:
在这里插入图片描述

总结的点:

1: 选取合适的数据类型 进行存放: 比如对象的存放可以存json也可以用hash,取决与你这么进行判断
2: key的选择 ,要合适确保唯一性
3: 选择合适的粒度,一些没用的信息就不要进行存放了
4: 一些key的时间注意设置,不易过长

添加缓存

流程:
在这里插入图片描述
代码:

public Result queryById(Long id) {
        //1:从redis 查询缓存
        String shopjson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
        //2:判断是否存在
        if (StrUtil.isNotBlank(shopjson)){
            //3:存在 返回
            Shop shop = JSONUtil.toBean(shopjson, Shop.class);
            return Result.ok(shop);
        }
        //4:不存在去数据库查
        Shop shop = query().eq("id", id).one();
        if (shop==null){
            //5:数据库不存在 报错
            return Result.fail("店铺不存在");
        }
        //6:数据库存在 存到缓存
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop));
        //7:返回
        return  Result.ok(shop);
    }

缓存更新:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最佳方案:

在这里插入图片描述

实施代码:

首先是 对查询的操纵:

接着是对 更新的操作:

注意要加事务注解

确保 更新数据库与删除缓存的原子性
在这里插入图片描述

缓存穿透

产生的原因

用户访问缓存和数据库中都不存在的数据时,缓存永远不会生效,这些请求则会打到数据库上。

解决方案:
方案1:缓存空对象:

在这里插入图片描述
实现逻辑:
在这里插入图片描述

原理:

就是没有找到,我也进行缓存,只不过存放的是null值,并且设置合适的过期时间,以防止内存占用过大。

方案2:布隆过滤

在这里插入图片描述
在这里插入图片描述

原理:

核心原理在于 布隆过滤器,其工作原理是,对数据库的key进行hash,然后根据hash来判断有没有,没有则不会进行,有则进行相应的操作。
这样导致,可能存在错误的判断情况。

方案1的实战:

流程的修改;

在这里插入图片描述

源码
public Result queryById(Long id) {
        //1:从redis 查询缓存
        String shopjson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
        //2:判断是否存在
        if (StrUtil.isNotBlank(shopjson)){
            //3:存在
            Shop shop = JSONUtil.toBean(shopjson, Shop.class);
            return Result.ok(shop);
        }
        //如果是空值(不是null 就是“”) 提前返回结果 步走数据库
        if (shopjson!=null){
            return Result.fail("店铺id不存在");
        }
        //4:不存在去数据库查
        Shop shop = query().eq("id", id).one();
        if (shop==null){
            //5:数据库不存在 进行 空值缓存
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        //6:数据库存在 存到缓存
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7:返回
        return  Result.ok(shop);
    }

总结:

在这里插入图片描述

缓存雪崩

第一个方案 就是 让 ttl 尽量 的分散 避免同时失效。
在这里插入图片描述

缓存击穿

在这里插入图片描述

方案一:互斥锁

在这里插入图片描述

方案二:逻辑过期

主要是 开辟一个新的线程,进行处理 ,先用旧的数据顶替一下
在这里插入图片描述

方案1实战

流程的修改

在这里插入图片描述

源码
private Result huancunJiChuan(Long id){
        //1:从redis 查询缓存
        String shopjson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
        //2:判断是否存在
        if (StrUtil.isNotBlank(shopjson)){
            //3:存在
            Shop shop = JSONUtil.toBean(shopjson, Shop.class);
            return Result.ok(shop);
        }
        //如果是空值(不是null 就是“”) 提前返回结果 步走数据库
        if (shopjson!=null){
            return Result.fail("店铺id不存在");
        }
        Shop shop = null;
        try {
            //4:不存在去数据库查
            //获取锁 失败休眠
            if (!tryLock(RedisConstants.LOCK_SHOP_KEY+id)){
                Thread.sleep(50);
                return huancunJiChuan(id);
            }
            //4.2成功开始操作数据库
            //模拟延迟
            Thread.sleep(200);
            shop = query().eq("id", id).one();
            if (shop==null){
                //5:数据库不存在 进行 空值缓存
                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                return Result.fail("店铺不存在");
            }
            //6:数据库存在 存到缓存
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException exception) {
            throw new RuntimeException();
        } finally {
            //8:释放锁
            unlock(RedisConstants.LOCK_SHOP_KEY+id);
        }

        //9:返回结果
        return  Result.ok(shop);
    }

方案2实战

流程修改

在这里插入图片描述

源码:

主要逻辑代码

   private final static ExecutorService EXECUTOR_SERVICE= Executors.newFixedThreadPool(10);
    private Result huancunJiChuanByLuoJI(Long id) {
        //1:从redis 查询缓存
        String redisDataJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
        //2:判断是否存在
        if (StrUtil.isBlank(redisDataJson)){
            //3 不存在 返空
            return Result.fail("店铺id不存在");
        }

        //4 命中  转换成对象 因为不论过不过其最后都要返回这个值
        RedisData redisData = JSONUtil.toBean(redisDataJson, RedisData.class);
        //由于这个是  shop是个 object类型 所以 给你会转成 jsonObject 需要手动转一下
       Shop shop=JSONUtil.toBean((JSONObject) redisData.getData(),Shop.class);
        //5 判断是否过期
        if (redisData.getExpireTime().isAfter(LocalDateTime.now())){
            //5.1 未过期  直接返回值
            return Result.ok(shop);
        }
        //5.2 过期 开始缓存重建
        //6缓存重建
        //6.1 获取互斥锁
        //6.2 判断是否成功获取互斥锁
        if (tryLock(RedisConstants.LOGIN_USER_KEY+id)){
            //6.3 成功 开启独立线程 构造缓存
                EXECUTOR_SERVICE.submit(()->{
                    try {
                        setShop2Redis(id,20L);
                    } catch (InterruptedException exception) {
                        exception.printStackTrace();
                    }finally {
                        //释放锁
                        unlock(RedisConstants.LOCK_SHOP_KEY+id);
                    }
                });
        }
        //6.4成功 失败都返回过期数据

        return Result.ok(shop);
//        return Result.ok(redisData.getData());


    }

上锁等小功能代码

//设置过期时间
    public void setShop2Redis(Long id,Long experSecondes) throws InterruptedException {
        //封装数据
        Shop shop=getById(id);
        //模拟延迟
        Thread.sleep(200);
        //封装过期时间
        RedisData redisData=new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(experSecondes));
        //写入redis
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }

    //上锁
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //由于 这个是包装类可能存在 null的情况 所以进行转化一下。
        return BooleanUtil.isTrue(flag);
    }

    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }


测试:

测试之前准备:
首先 :在执行之前 写入数据:时间是 10秒过期 我们等十秒后开始测试在这里插入图片描述
这是执行后缓存与数据库的情况
在这里插入图片描述
然后 修改数据库,让其与缓存不一致
在这里插入图片描述
预期效果
因为实现了逻辑过期 并且 在执行数据库的时候会进行 200毫秒的延迟 ,我们使用 jmter进行测试 前面的数据 应该还是 老数据zyc 而过了200毫秒后的数据应该是 wyy了

实际结果;
在这里插入图片描述
在这里插入图片描述

缓存工具的封装

对于阅读了上述的这么多案列,我们不难发现很多的方法是重复的可以进行封装处理的,比如几个常见的:
在这里插入图片描述
按照自己需求进行相关功能的封装。

相关封装技巧看这里

点我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis具有多种实际应用场景,包括但不限于以下几个方面: 1. 缓存Redis最常见的用途就是作为缓存层,将经常访问的数据存储在内存中,以提高读取速度和减轻后端数据库的负载。通过使用Redis的高速读写能力,可以大幅提升系统的响应性能。 2. 会话存储:Redis可以用作会话存储,将用户的会话数据存储在内存中,实现快速的会话访问和管理。这对于需要处理大量并发用户请求的应用程序特别有用。 3. 消息队列:Redis支持发布-订阅模式(Pub/Sub),可以作为消息队列系统使用。通过将消息发布到特定的频道,不同的客户端可以订阅这些频道并接收实时的消息推送。这在实时通知、实时聊天和异步任务处理等场景中非常有用。 4. 计数器和排行榜:Redis提供了原子性操作和快速的计数功能,可用于实现计数器和排行榜功能。例如,在社交媒体应用中,可以使用Redis来实时统计用户的粉丝数量或文章的点赞数,并根据这些数据生成排行榜。 5. 地理位置服务:Redis的地理位置功能(Geo)可以存储和查询地理位置信息,如坐标和半径范围内的位置。这使得Redis在构建地理位置服务(如附近的人、门店定位等)时非常有用。 总之,Redis是一个功能强大且灵活的内存数据库,可用于多种实际应用场景,包括缓存、会话存储、消息队列、计数器和排行榜,以及地理位置服务等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值