Redis缓存、雪崩、穿透,数据一致性

  1. 缓存雪崩概念

故障原因:redis挂了 事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃 事中:本地cache缓存 + hystrix限流&降级,避免MySQL被打死 事后:redis持久化,快速恢复缓存数据

故障原因2:缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效 将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。(和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。)

2、缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。解决办法有两个:

查询数据库不存在,则在redis中存一份Null值,避免访问Mysql

采用布隆过滤器,将List数据装载入布隆过滤器中,访问经过布隆过滤器,存在才可以往db中查询。于是在内存中就可以拦截恶意请求。

解决方案:布隆过滤器
布隆过滤器的使用方法,类似java的SET集合,用来判断某个元素(key)是否在某个集合中。和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。

使用步骤:1、将List数据装载入布隆过滤器中

private BloomFilter bf =null;

//PostConstruct注解对象创建后,自动调用本方法
@PostConstruct
public void init(){
    //在bean初始化完成后,实例化bloomFilter,并加载数据
    List entities= initList();
    //初始化布隆过滤器
    bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), entities.size());
    for (Entity entity : entities) {
        bf.put(entity.getId());
    }
}

2、访问经过布隆过滤器,存在才可以往db中查询

 public Provinces query(String id) {
        //先判断布隆过滤器中是否存在该值,值存在才允许访问缓存和数据库
        if(!bf.mightContain(id)){
            Log.info("非法访问"+System.currentTimeMillis());
            return null;
        }
        Log.info("数据库中得到数据"+System.currentTimeMillis());
        Entity entity= super.query(id);
        return entity;
    }

这样当外界有恶意攻击时,不存在的数据请求就可以直接拦截在过滤器层,而不会影响到底层数据库系统。

  1. 缓存击穿概念
    一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

解决方案
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

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

    static Lock reenLock = new ReentrantLock();
    public List getData04() throws InterruptedException {
        List result = new ArrayList();
        // 从缓存读取数据
        result = getDataFromCache();
        if (result.isEmpty()) {
            if (reenLock.tryLock()) {
                try {
                    System.out.println("我拿到锁了,从DB获取数据库后写入缓存");
                    // 从数据库查询数据
                    result = getDataFromDB();
                    // 将查询到的数据写入缓存
                    setDataToCache(result);
                } finally {
                    reenLock.unlock();// 释放锁
                }

            } else {
                result = getDataFromCache();// 先查一下缓存
                if (result.isEmpty()) {
                    System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
                    Thread.sleep(100);// 小憩一会儿
                    return getData04();// 重试
                }
            }
        }
        return result;
    }

链接:https://www.jianshu.com/p/87896241343c

4、数据库与缓存一致性

Cache Aside Pattern,基本都采用如下的缓存模式:

读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应

更新的时候,先更新数据库,再删除缓存(先删除缓存在更新数据库会存在脏数据)

参考:
https://www.jianshu.com/p/dc09f86ca4ba
https://www.jianshu.com/p/cbc39abb6b94

你可能感兴趣的:(Redis缓存、雪崩、穿透,数据一致性)