redis学习笔记(四)- 缓存雪崩、缓存击穿、缓存穿透

120 阅读4分钟

缓存雪崩

发生

Redis主机挂了,redis全盘崩溃,比如redis中有大量数据同时过期

解决

image.png Redis缓存集群实现高可用 -- (主从+哨兵、redis cluster)。

Ehcache本地缓存 + hystrix或者阿里sentinel限流&降级。

开启redis持久化机制aof、rdb,尽快恢复缓存集群。

缓存穿透

是什么

请求去查询一条数据,先redis后mysql发现都查询不到该条数据,但是每次都会打到数据库上面去,导致后台数据库压力暴增,这种现象我们称为缓存穿透,这个redis变成一个摆设。

简单来说就是本来无一物,既不在redis缓存中,也不在数据库中。

危害

第一次来查询后,一般我们有会写redis机制

第二次来查的时候redis就有了,偶尔出现穿透现象一般情况无关紧要。

解决

方案1 空对象缓存或者缺省值

一般情况下是没有问题的,一旦发生缓存穿透,我们就可以针对查询的数据,在redis中缓存一个空值或事和业务层协商确定的缺省值(例如,库存的缺省值可以设为0)。紧接着,应用发送的后续请求再进行查询时,就可以直接从redis中读取空值或缺省值,返回给业务应用了,避免了把大量请求发送给数据库处理,保持了数据库的正常运行。

但是,存在黑客或者恶意攻击的情况,

黑客会对你的系统进行攻击,拿一个不存在的ID去查询数据,会产生大量的请求到数据库去查询。可能导致你的数据库由于压力太大而宕掉。

ID相同打你的系统:第一次打到mysql,空对象缓存后第二次就返回nulll了,避免mysql被攻击,不用再到数据库去走一圈了。

ID不同打你的系统:由于存在空对象和缓存会写(看自己业务不限死),redis中的无关紧要的key也会越来越多(记得设置过期时间)。

方案2 :Google布隆过滤器guava解决缓存穿透

Guava中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。

Guava`s bloomFilter源码剖析

github.com/google/guav…

方案3:redis布隆过滤器解决缓存穿透

Guava缺点说明:

guava提供的布隆过滤器的实现还是很不错的(想要详细了解的可以看下他的源码实现),但是他有一个重大的缺陷就是只能单机使用,而现在互联网一般都是分布式的场景。

为了解决这个问题,我们就需要用到redis中的布隆过滤器。

案例:白名单过滤器

白名单架构说明

image.png 误判问题,但是概率小可以接受,不能从布隆过滤器删除

全部合法的key都需要放入过滤器+redis里面,不然数据就是返回null

代码:

public class RedissonBloomFilterDemo2 {
    public static final int _1W = 10000;

    //布隆过滤器里预计要插入多少数据
    public static int size = 100 * _1W;
    //误判率,它越小误判的个数也就越少
    public static double fpp = 0.03;

    static RedissonClient redissonClient = null;
    static RBloomFilter rBloomFilter = null;

    static {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.111.147:6379").setDatabase(0);
//构造redisson
        redissonClient = Redisson.create(config);
//通过redisson构造rBloomFilter
        rBloomFilter = redissonClient.getBloomFilter("phoneListBloomFilter", new StringCodec());

        rBloomFilter.tryInit(size, fpp);

// 1测试  布隆过滤器有+redis有
        rBloomFilter.add("10086");
        redissonClient.getBucket("10086", new StringCodec()).set("chinamobile10086");

// 2测试  布隆过滤器有+redis无
//rBloomFilter.add("10087");

        //3 测试 ,都没有

    }

    public static void main(String[] args) {
        String phoneListById = getPhoneListById("10087");
        System.out.println("------查询出来的结果: " + phoneListById);

//暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        redissonClient.shutdown();
    }

    private static String getPhoneListById(String IDNumber) {
        String result = null;

        if (IDNumber == null) {
            return null;
        }
//1 先去布隆过滤器里面查询
        if (rBloomFilter.contains(IDNumber)) {
//2 布隆过滤器里有,再去redis里面查询
            RBucket rBucket = redissonClient.getBucket(IDNumber, new StringCodec());
            result = rBucket.get();
            if (result != null) {
                return "i come from redis: " + result;
            } else {
                result = getPhoneListByMySQL(IDNumber);
                if (result == null) {
                    return null;
                }
// 重新将数据更新回redis
                redissonClient.getBucket(IDNumber, new StringCodec()).set(result);
            }
            return "i come from mysql: " + result;
        }
        return result;
    }

    private static String getPhoneListByMySQL(String IDNumber) {
        return "chinamobile" + IDNumber;
    }
}

总结:

image.png

黑名单使用

image.png

缓存击穿

是什么

大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去,简单说就是热点key突然失效了,暴打mysql。

危害

会造成某一时刻数据库请求量过大,压力剧增。

解决

方案1:对于访问频繁的热点key,干脆就不设置过期时间

方案2:互斥更新、随机避退、差异失效时间

方案3:互斥独占锁防止击穿

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

image.png