什么是Redis的缓存击穿、缓存穿透、缓存雪崩?

缓存雪崩

原因:

大量的缓存在一瞬间失效,导致了大量的查询瞬间落到了数据库上,会有可能导致数据库宕机,

解决方案:

1、加锁排队

使用mutex互斥锁,redis的setnx去set一个mutex key,当操作返回成功时,再进行加载数据库的数据并设置缓存,否则,就重试整个get方法。

2、数据预热

系统上线之后,将相关的缓存数据直接加载到缓存系统。这样可以避免用户请求数据的时候直接请求数据库,然后再将数据缓存的问题。

我们通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动出发加载缓存不同的key。

3、双层缓存策略

C1为原始缓存,C2为拷贝缓存。C1失效时,去访问C2。C1缓存失效时间设置为短期,C2设置为长期

4、定时更新缓存策略

实效性要求不高的缓存,容器启动初始化加载,采用定时任务更新或移除缓存

5、设置不同的过期时间,让缓存时间点尽量均匀

缓存击穿

原因:

用户要读取的数据,缓存中没有(热点key在失效的瞬间),同时并发用户特别多的时候,数据库的压力会瞬间倍增

解决方案:

1、使用mutex互斥锁

让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据。

单机环境:用synchronized或者lock处理

分布式环境:使用分布式锁,memcache的add,redis的setnx,zookeeper的添加节点

2、设置key永不过期
3、缓存屏障

使用countDownLatch和atomicInteger.compareAndSet()方法实现轻量级锁

class MyCache{
    private ConcurrentHashMap<String, String> map;
    private CountDownLatch countDownLatch;
    private AtomicInteger atomicInteger;

    public MyCache(ConcurrentHashMap<String, String> map, CountDownLatch countDownLatch,
                   AtomicInteger atomicInteger) {
        this.map = map;
        this.countDownLatch = countDownLatch;
        this.atomicInteger = atomicInteger;
    }

    public String get(String key){

        String value = map.get(key);
        if (value != null){
            System.out.println(Thread.currentThread().getName()+"\t 线程获取value值 value="+value);
            return value;
        }
        // 如果没获取到值
        // 首先尝试获取token,然后去查询db,初始化化缓存;
        // 如果没有获取到token,超时等待
        if (atomicInteger.compareAndSet(0,1)){
            System.out.println(Thread.currentThread().getName()+"\t 线程获取token");
            return null;
        }
        // 其他线程超时等待
        try {
            System.out.println(Thread.currentThread().getName()+"\t 线程没有获取token,等待中。。。");
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 初始化缓存成功,等待线程被唤醒
        // 等待线程等待超时,自动唤醒
        System.out.println(Thread.currentThread().getName()+"\t 线程被唤醒,获取value ="+map.get("key"));
        return map.get(key);
    }
    public void put(String key, String value){

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        map.put(key, value);

        // 更新状态
        atomicInteger.compareAndSet(1, 2);

        // 通知其他线程
        countDownLatch.countDown();
        System.out.println();
        System.out.println(Thread.currentThread().getName()+"\t 线程初始化缓存成功!value ="+map.get("key"));
    }
}

class MyThread implements Runnable{

    private MyCache myCache;

    public MyThread(MyCache myCache) {
        this.myCache = myCache;
    }

    @Override
    public void run() {
        String value = myCache.get("key");
        if (value == null){
            myCache.put("key","value");
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {

        MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0));

        MyThread myThread = new MyThread(myCache);

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(myThread);
        }
    }
}

缓存穿透

原因:

用户要读取的数据,缓存中没有,同时并发用户特别多的时候,数据库的压力会瞬间倍增

解决方案:

1、缓存空对象

在一条查询返回的数据为空的时候,我们把这个空数据进行缓存,缓存时间一般不超过五分钟

2、布隆过滤器

占用内存空间很小,位存储;性能特别高,使用key的hash判断key存不存在 将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

缓存降级

原因:

就是缓存失效的时候,或者缓存服务挂了,我们不走数据库,直接去访问内存部分的数据缓存或者默认数据。

例如首页中的数据,在走了缓存降级策略之后,可能会对业务有一定的影响。这是有损的操作,尽可能的不使用缓存降级。

缓存预热

如果不对数据进行预热,那么redis的初始状态为空,系统上线初期对于高并发的流量都会访问到数据库,对数据库造成最直接的压力。

预热方案

1、数据量不大的时候,工程启动的时候进行加载缓存动作

2、数据量大的时候,设置一个定时任务(或者脚本),进行缓存的刷新

3、数据量太大的时候,优先保证热点数据进行提前加载到缓存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值