缓存失效问题

一、缓存穿透

缓存穿透 是指 查询一个一定不存在的数据 ,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
在流量大时 ,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。
解决:
缓存空结果、并且设置短的过期时间。

二、缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB DB 瞬时压力过重雪崩。
解决:
原有的失效时间基础上增加一个随机值 ,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

三、缓存击穿

对于一些设置了过期时间的 key ,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“ 热点 的数据。
这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到 db ,我们称为缓存击穿。
解决:
加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db
缓存
db
本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有的分布式服务,必须使用分布式锁

本地锁 

 本地锁流程如下:

       // 本地锁
        synchronized (this) {
            // 查询数据库前先再查一次缓存
            ......
            System.out.println("查询了数据库");
            // 查询数据库的业务
            ......

            // 在放开锁之前放入redis,因为存放进redis也是一次网络交互,如果放在锁外面也需要时间(此时可能被别的线程乘虚而入又查询数据库)
            ......
        }

 本地锁流程图如下:

分布式锁-使用redis存储key

锁即是lock,每次都往redis中存储key---使用setnu(原子操作),如果存放成功,那么就是拿到锁了

阶段一

问题:
1 setnx 占好了位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁
解决:
设置锁的自动过期,即使没有删除,会自动删除

阶段二

问题:
1 setnx 设置好,正要去设置过期时间,宕机。又死锁了。
解决:
设置过期时间和占位必须是原子的。 redis 支持使用 setnx ex命令(占位的同时设置过期时间)

阶段三

问题:
1 、删除锁直接删除???
如果由于业务时间很长,锁自己过期了,我们直接删除,有可能把别人正在持有的锁删除了。
解决:
占锁的时候,值指定为 uuid ,每个人匹配是自己的锁才删除

阶段四

问题:
1 、如果正好判断是当前值,正要删除锁的时候,锁已经过期,别人已经设置到了新的值。那么我们删除的是别人的锁
解决:
删除锁必须保证原子性。使用 redis+Lua 脚本完成
最终版本:
1、每个线程存放的锁要有自己的标识(uuid等)
2、占锁时存放和设置时间需要是原子性的(EX NX)
3、删锁时判断是否是自己的锁和删除操作需要是原子性的(LUA脚本,脚本一起成功或者一起失败)
        // TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有的分布式服务,必须使用分布式锁

        // 分布式锁 redis存lock Key来实现
        // 先去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            Map<String, List<Catelog2Vo>> result ;
            //占坑成功
            try {
                result = getDataFromDb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                // 删除key
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                        Arrays.asList("lock"), uuid);
            }
            return result;
        } else {
            // 加锁失败  重试
            // 这里也可休眠一段时间
            return getCatelogJsonFromDBWithRedisLock(); //采用自旋的方式
        }

猜你喜欢

转载自blog.csdn.net/m0_62946761/article/details/132136201