【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查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

相关推荐
yangpan0112 小时前
colmap调试学习(二)--feature_matching
学习
GLDbalala2 小时前
GPU PRO 5 - 1.1 Per-Pixel Lists for Single Pass A-Buffer 笔记
笔记
载数而行5202 小时前
QT系列,对象树 栈和堆 QDebug以及日志打印
c++·qt·学习
red_redemption2 小时前
自由学习记录(127)
学习
庭前云落2 小时前
从零开始的OpenZeppelin学习 2| ERC20-permit、erc20pausable
学习·区块链
zyb11475824333 小时前
Redis的学习
数据库·redis·学习
小白自救计划3 小时前
【计算机视觉】学习历程
人工智能·学习·计算机视觉
ding_zhikai3 小时前
【Web应用开发笔记】Django笔记9:Django部署注意事项补充
笔记·后端·python·django
怪侠_岭南一只猿3 小时前
爬虫阶段一实战练习题:爬取豆瓣电影 Top250 复盘
css·经验分享·爬虫·python·学习·正则表达式