【Redis学习笔记】(二)短信登录+商户查询缓存

1 短信登录

基于Session实现:发送验证码;验证码登录注册;校验登录状态(使用拦截器实现,保存在ThreadLocal中);UserDTO类隐藏用户敏感信息


session共享问题:多台tomcat不共享session存储空间,当请求切换到不同服务器会导致数据丢失。

基于Redis实现共享session登录

发送验证码(以手机号为key,保存验证码到Redis);

哈希类型可以将对象中的每个字段独立存储,且内存占用更少;使用随机token为key存储用户数据到Redis

2 商户查询缓存

01缓存

数据交换的缓冲区,临时存储数据的地方,读写性能较高。用来衡量cpu性能

浏览器缓存(静态资源);应用层缓存(tomcat);数据库缓冲(索引);cpu缓存;磁盘缓存

作用:降低后端负载;提高IO效率,降低响应时间。

成本:数据一致性成本;代码维护成本;运维成本。

02添加Redis缓存

在客户端和数据库添加中间层缓存

复制代码
public Result queryById(Long id) {
        String key=CACHE_SHOP_KEY+id;
        //1从redis查询缓冲
        String shopJson=stringRedisTemplate.opsForValue().get(key);
        //2判断存在
        if(StrUtil.isNotBlank(shopJson)){
            //3存在直接返回
            Shop shop=JSONUtil.toBean(shopJson,Shop.class);
            return Result.ok(shop);
        }
        //4不存在查数据库
        Shop shop=getById(id);
        //5不存在返回错误
        if (shop==null){
            return Result.fail("店铺不存在");
        }
        //6存在写入redis返回
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        return Result.ok(shop);
    }

03缓存更新策略

内存淘汰:内存不足时自动淘汰部分数据,下次查询时更新缓存;一致性差;无维护成本。

超时剔除:给缓存数据添加TTL,到期自动删除缓存;一致性一般;维护成本低。

主动更新:自己编写业务逻辑,修改数据库的同时更新缓存;一致性好;维护成本高。


低一致性需求,使用内存淘汰。例如店铺类型查询

高一致性需求,使用主动更新+超时剔除结合。例如店铺详情查询


主动更新策略

缓存旁路(常用):由缓存的调用者在更新数据库的同时删除缓存;保证缓冲和数据库操作同时成功失败,单体系统将缓冲和数据库放在一个事务,分布式系统利用分布式事务方案;一般先操作数据库再删除缓存

读穿透:缓存和数据库整合成一个服务,由服务来维护一致性,调用者调用服务

写回:调用者只操作缓存,有其他线程异步地将缓存数据持久化到数据库

实现缓存和数据库的双写一致

复制代码
    @Override
    @Transactional
    public Result update(Shop shop) {
        Long id=shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1更新数据库
        updateById(shop);
        //2删除缓冲
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        
        return Result.ok();
    }

04缓存问题

**缓存穿透:**客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,所有请求都到达数据库。

解决方案:缓存空对象null(实现简单;额外内存消耗,可能造成短期不一致);布隆过滤(请求先进入布隆过滤器,如果数据不存在则拒绝;内存占用少;实现复杂,存在误判可能)

主动方案:增加id复杂度,避免被猜测id概率;做好数据的基础格式校验;加强用户权限校验

复制代码
        //新增:判断命中值是否为空
        if(shopJson!=null){
            return Result.fail("店铺信息不存在");
        }
        //4不存在查数据库
        Shop shop=getById(id);
        //5不存在返回错误
        if (shop==null){
            //新增:将空值写入redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }

缓存雪崩:在同一时段有大量的缓存key同时失效,或Redis服务宕机,导致大量请求到达数据库。

解决方案:给不同的key添加TTL随机值;利用Redis集群提高服务的可用性;给缓存业务添加降级限流策略;给缓存业务添加降级限流策略;给业务添加多级缓存

缓存击穿:也叫热点key问题,指被高并发访问且缓存重建业务较复杂的key突然失效,无数的请求访问在瞬间冲击数据库。

解决方案:互斥锁(获取锁失败休眠一会再重试;没有额外内存消耗,一致性,实现简单;性能受影响,有死锁风险);逻辑过期(获取锁后由新线程执行查询并重置逻辑过期时间;优缺相反)

复制代码
    public Shop queryWithMutex(Long id){//基于互斥锁解决缓存击穿
        String key=CACHE_SHOP_KEY+id;
        //1从redis查询缓冲
        String shopJson=stringRedisTemplate.opsForValue().get(key);
        //2判断存在
        if(StrUtil.isNotBlank(shopJson)){
            //3存在直接返回
            return JSONUtil.toBean(shopJson,Shop.class);
        }
        //新增:判断命中值是否为空
        if(shopJson!=null){
            return null;
        }
        //实现缓存重建
        //11获取互斥锁
        String lockKey="lock:shop:"+id;
        Shop shop= null;
        try {
            boolean isLock=tryLock(lockKey);
            //22判断是否获取成功
            if(!isLock){
                //33失败休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //44成功根据id查询数据库
            //4不存在查数据库
            shop = getById(id);
            Thread.sleep(200);//模拟重建的延时
            //5不存在返回错误
            if (shop==null){
                //新增:将空值写入redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            //6存在写入redis返回
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //55释放互斥锁
            unlock(lockKey);
        }
        return shop;
    }

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    //基于逻辑过期解决缓存击穿
    public Shop queryWithLogicalExpire(Long id){
        String key=CACHE_SHOP_KEY+id;
        //1从redis查询缓冲
        String shopJson=stringRedisTemplate.opsForValue().get(key);
        //2判断存在
        if(StrUtil.isBlank(shopJson)){
            //3存在直接返回
            return null;
        }
        //新增:存在判断是否过期
        //11先把json反序列化为对象
        RedisData redisData=JSONUtil.toBean(shopJson,RedisData.class);
        Shop shop=JSONUtil.toBean((JSONObject) redisData.getData(),Shop.class);
        LocalDateTime expireTime=redisData.getExpireTime();
        //22判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            //33未过期直接返回店铺信息
            return shop;
        }
        //44已过期需要换成重建
        //441获取互斥锁
        String lockKey=LOCK_SHOP_KEY+id;
        boolean isLock=tryLock(lockKey);
        //442判断是否获取锁成功
        if(isLock){
            //4421成功,开启线程实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() ->{
                try {
                    this.saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //443返回过期的商铺信息
        return shop;
    }

05缓存工具封装

封装Redis工具类

方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间

方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题

方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题

方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

相关推荐
Tutankaaa17 小时前
从被动接受到主动挑战:知识竞赛如何重塑学习价值
人工智能·经验分享·笔记·学习
房开民18 小时前
modbus相关学习
网络·学习
STC_USB_CAN_805119 小时前
菜单学习,科学计算器使用【TFT240*320彩屏+实际键盘】@Ai8051U,ST7789
单片机·学习·51单片机
三棱球19 小时前
App逆向学习笔记(三)——Android开发入门课
android·笔记
handler0120 小时前
拒绝权限报错!三分钟掌握 Linux 权限管理
linux·c语言·c++·笔记·学习
xiaotao13120 小时前
02-机器学习基础: 无监督学习——scikit-learn实战与模型管理
学习·机器学习·scikit-learn
阿Y加油吧20 小时前
算法实战笔记:LeetCode 169 多数元素 & 75 颜色分类
笔记·算法·leetcode
ouliten20 小时前
cuda编程笔记(39)--Asynchronous Barriers(异步屏障)
笔记·cuda
U盘失踪了21 小时前
Go 结构体
笔记·golang
hipolymers1 天前
C语言怎么样?难学吗?
c语言·数据结构·学习·算法·编程