Redis缓存穿透、击穿以及雪崩的理解

一、缓存穿透

1.含义

       缓存穿透是指用户获取数据的时候,发现redis数据库中没有数据,进而从持久层数据库中进行查询。若未查询出来数据,则查询失败。低并发的条件下,不会出现问题;若在高并发的条件,当大部分用户访问时,若缓存没有命中,则大量的请求都落在持久层数据库,这时数据库将承受着很大的压力,甚至导致崩溃。简而言之,缓存穿透就是访问绕过了缓存,进而直接访问持久层数据库,最终导致持久层数据库崩溃。(缓存穿透是指缓存和持久层数据库中都不存在数据)

2. 图解

3. 解决方案 

 (1)  缓存空对象

       此方案针对是只访问某些固定的值有效,当访问请求的时候,发现缓存中并没有命中,则去持久层数据库中获取,若发现持久层数据库中并没有数据,则将数据缓存成空对象,放至缓存中,当下一次同样的请求过来后,能够直接从缓存中获取,不再访问持久层数据库,解决该问题。

(2)布隆过滤器

       当访问的时候,用户访问的请求不是落在某些固定的值的时候,采用上述的缓存空对象方法并不适用,因为访问不同的key,将会都达到持久层数据库中进行获取,上述的方法反而会带来消耗大量内存等问题。此时可以采用布隆过滤器进行解决。

       布隆过滤器是采用了是判断key是否存在于集合的方式,来判断持久层数据库中是否存在该值的方式进行。其采用了二进制向量的方式,对key值进行hash运算,而后取余,得到的数据通过二进制向量的下标,获取该值,若值为1,则代表数据可能在持久层数据库中存在,而值为0的时候,代表数据一定不存在。其采用以错误率换取时间的方式,来进行数据的过滤。其数据结构的图解如下:

       由于布隆算法采用的hash进行计算,故降低错误率的方法就是减少hash碰撞,布隆过滤器减少hash碰撞的两种方法有:1. 使二进制数组尽可能的大 2.多次hash,将多次获取的结果取与。

       算法代码如下:

public class BloomFilter {

    // 二进制数组的长度
    private static final int BIT_SIZE = 2 << 24;

    // 计算出不同的hash, hash次数为5次
    private static final int[] PROTONS = new int[]{3, 7, 15, 31, 63};

    // 二进制数组
    private BitSet bit = new BitSet(BIT_SIZE);

    // hash值
    private Hash[] hash = new Hash[PROTONS.length];

    // 构造函数
    public BloomFilter() {
        for (int i = 0; i < PROTONS.length; i++) {
            hash[i] = new Hash(BIT_SIZE, PROTONS[i]);
        }
    }

    // 添加key
    public void add(String key) {
        for (Hash hash1 : hash) {
            bit.set(hash1.hash(key), Boolean.TRUE);
        }
    }

    // 判断key是否存在
    public boolean existKey (String key) {
        boolean ret = Boolean.TRUE;
        for (Hash hash1 : hash) {
            ret = ret & bit.get(hash1.hash(key));
        }
        return ret;
    }

    // 计算hash内部类
    private class Hash {
        // 长度
        private int cap;
        // 质子
        private int proton;

        public Hash(int cap, int proton) {
            this.cap = cap;
            this.proton = proton;
        }

        // 计算hash并取余
        public int hash(String key) {
            int result = 0;
            if (key == null) {
                return (cap - 1) & result;
            }
            for (int i=0; i< key.length(); i++) {
                result = this.proton * result + key.charAt(i);
            }
            return (cap - 1) & result;
        }

    }

}

二、缓存击穿

1. 含义

       缓存击穿是指热点数据在大量请求同时访问的时候,缓存中的数据过期,大量的请求直接去持久层数据库中获取数据,导致持久层数据库压力过大甚至崩溃。(缓存击穿是指数据在缓存中不存在,在持久层数据库中存在的情况)

2. 图解

3. 解决方案

(1)  设置热点数据永不过期。

(2)  利用互斥锁访问持久层数据库。代码如下:

public class Mutex {

    private ReentrantLock reentrantLock = new ReentrantLock();
    
    public Object getData(String key) throws InterruptedException {
        Object value = getDataFromRedis(key);
        if (value == null) {
            // 获取锁
            if (reentrantLock.tryLock()) {
                // 从持久层数据库中获取数据
                value = getDataFromDb(key);
                if (value != null) {
                    // 添加数据至redis中
                    setDataToRedis(key, value);
                }
                // 释放锁
                reentrantLock.unlock();
            } else {
                // 休眠100ms,然后重新访问
                Thread.sleep(100);
                value = getData(key);
            }
        }
        return value;
    }

    private Object getDataFromRedis(String key) {
        // 从redis中获取数据(此处不实现)
        return null;
    }

    private Object getDataFromDb(String key) {
        // 从持久层数据库中获取数据(此处不实现)
        return null;
    }

    private void setDataToRedis(String key, Object value) {
        // 添加数据至redis中,此处不实现
    }

}

三、缓存雪崩

1. 含义

       缓存雪崩是指缓存出现宕机或者数据在一瞬间全部失效,大量的并发请求全部从持久层数据库上获取数据,导致持久层数据库压力过大设置崩溃。(缓存雪崩是指数据在缓存中不存在,在持久层数据库中存在的情况)

2. 图解

3. 解决方案

(1)缓存高可用

        搭建缓存集群,当一台机器宕机,其他的机器还可以使用。

(2)限流降级

        在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。

(3)数据预热

        在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

四、总结

      本博客借鉴了https://blog.csdn.net/cdm0881/article/details/105197011/,这是本人的第一个博客,望一起学习,一起进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值