美文网首页中间件SQL
Redis缓存击穿、穿透、雪崩解决方案

Redis缓存击穿、穿透、雪崩解决方案

作者: f0cf20ca7354 | 来源:发表于2019-10-10 16:06 被阅读0次

    1、缓存处理流程

    接收到查询数据请求时,优先从缓存中查询,若缓存中有数据,则直接返回,若缓存中查不到则从DB中查询,将查询的结果更新到缓存中,并返回查询结果,若DB中查不到,则返回空数据


    缓存处理流程.png

    2、缓存穿透

    当缓存与数据库中都不存在该数据时,由于当数据库查询不到数据就不会写入缓存,这个时候如果用户不断的恶意发起请求,就会导致这个不存在的数据每次请求都会查询DB,请求量大的情况下,就会导致DB压力过大,直接挂掉。

    解决方案:

    1、当查询返回一个空数据时,直接将这个空数据存到缓存中,过期时间不宜设置过长,建议不超过5分钟
    2、采用布隆过滤器:将所有可能存在数据,分别通过多个哈希函数生成多个哈希值,然后将这些哈希值存到一个足够大的bitmap中,此时一个一定不存在的数据就会被这个bitmap拦截,从而减少了数据库的查询压力。
    参考链接:https://www.jianshu.com/p/2104d11ee0a2

    3、缓存击穿

    某一个数据缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,严重情况下会直接挂掉。

    解决方案:

    1、添加互斥锁:

    • ReentrantLock公平锁
    • 根据key值加锁,这样线程之间会不影响,不会因为某一个线程获取了锁,其它线程就处于等待时间,也就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据
      2、设置热点数据永不过期(物理上的不过期、“逻辑上”的不过期(缓存到期动态构建缓存))
    简单的互斥锁例子:
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Autowired
        private Jedis               jedis;
        private final String        MUTEX_KEY = "MUTEX_";
    
        public String getData(String key) throws InterruptedException {
            String value = stringRedisTemplate.opsForValue().get(key);
            //缓存失效
            if (StringUtils.isBlank(value)) {
                //设置分布式锁,只允许一个线程去查询DB,同时指定过期时间为1min,防止del操作失败,导致死锁,缓存过期无法加载DB数据
                if (tryLock(MUTEX_KEY + key, 60L)) {
                    //从数据库查询数据,将查询的结果缓存起来
                    value = getValueFromDB();
                    stringRedisTemplate.opsForValue().set(key, value);
    
                    //释放分布式锁
                    stringRedisTemplate.delete(MUTEX_KEY + key);
                } else {
                    //当锁被占用时,睡眠5s继续调用获取数据请求
                    Thread.sleep(5000);
                    getData(key);}
            }
            return value;
        }
    
        /**
         * redis实现分布式事务锁 尝试获取锁
         * 
         * @param lockName  锁
         * @param expireTime 过期时间
         * @return
         */
        public Boolean tryLock(String lockName, long expireTime) {
            //RedisCallback redis事务管理,将redis操作命令放到事务中处理,保证执行的原子性
            String result = stringRedisTemplate.opsForValue().getOperations().execute(new RedisCallback<String>() {
    
                /**
                 * @param key 使用key来当锁,因为key是唯一的。
                 * @param value 请求标识,可通过UUID.randomUUID().toString()生成,解锁时通value参数可识别出是哪个请求添加的锁
                 * @param nx 表示SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
                 * @param ex 表示过期时间的单位是秒
                 * @param time 表示过期时间
                 */
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    return jedis.set(lockName, UUID.randomUUID().toString(), "NX", "EX", expireTime);
                }
            });
    
            if ("OK".equals(result)) {
                return true;
            }
            return false;
        }
    
        public String getValueFromDB() {
            return "";
        }
    

    3、缓存雪崩

    缓存中大批量的数据都到了过期时间,从而导致查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同,缓存击穿是指某一条数据到了过期时间,大量的并发请求都来查询这一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库

    解决方案:

    1、设置热点数据永不过期
    2、缓存数据的过期时间设置随机,可以在原有的过期时间上加上一个随机值,比如1-3min,防止同一时间大量缓存数据集体失效,导致数据库压力过大。
    3、如果是分布式部署缓存数据库,可将热点数据分别存放到不同的缓存数据库中,避免某一点由于压力过大而down掉。

    相关文章

      网友评论

        本文标题:Redis缓存击穿、穿透、雪崩解决方案

        本文链接:https://www.haomeiwen.com/subject/srsgpctx.html