【面试】什么是缓存击穿、缓存穿透、缓存雪崩

一 缓存击穿

        缓存击穿是指某些热点数据在缓存中失效,且在失效瞬间有大量并发请求同时去访问该数据,导致所有请求都落在数据库上,进而对数据库造成极大压力。

        例如某一热点数据频繁被访问,缓存过期后,大量请求在缓存没有命中时同时去查询数据库,从而使数据库的负载瞬间升高。

解决方案:

1.使用互斥锁(Mutex)机制:在缓存过期时,只允许一个线程去加载数据,其它线程等待,等数据加载完成后,再从缓存中获取数据。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

public class CacheWithMutex {
    private ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
    private ReentrantLock lock = new ReentrantLock();

    // 模拟数据库查询
    private String loadFromDB(String key) {
        // 模拟数据库查询
        return "ValueFromDB";
    }

    public String getValue(String key) {
        String value = cache.get(key);
        if (value == null) {
            // 获取锁
            lock.lock();
            try {
                // 双重检查防止重复查询数据库
                value = cache.get(key);
                if (value == null) {
                    // 缓存中没有,查询数据库
                    value = loadFromDB(key);
                    cache.put(key, value);
                }
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
        return value;
    }
}

2.提前设置缓存过期时间:可以给热点数据提前设置不同的过期时间,避免同一时刻大量缓存同时失效。

3.使用永不过期策略:对于特别重要的热点数据,可以考虑将其设置为不过期,但需要定时主动更新缓存。


二 缓存穿透

        缓存穿透是指查询的数据既不存在缓存中也不存在数据库中,用户请求每次都会打到数据库。由于缓存机制不生效,导致每个请求都会穿透缓存直接查询数据库,极易对数据库造成压力。

        例如用户请求了一个数据库中不存在的数据,且该数据在缓存中也没有命中。由于缓存没有存储该不存在的数据,每次查询都会直接请求数据库。

解决方案:

1.缓存空值:当数据库查询结果为空时,将这个空结果也放入缓存中,并设置较短的过期时间(如5分钟),避免频繁穿透到数据库。

public String getValue(String key) {
    String value = cache.get(key);
    if (value == null) {
        value = loadFromDB(key);
        if (value == null) {
            // 将空值放入缓存,并设置一个较短的过期时间
            cache.put(key, "NULL");
        } else {
            cache.put(key, value);
        }
    }
    return "NULL".equals(value) ? null : value;
}

2.使用布隆过滤器(Bloom Filter):可以在缓存前增加一个布隆过滤器来判断请求的数据是否存在。布隆过滤器可以高效地判断一个数据是否不存在,大幅减少无效查询。

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.util.concurrent.ConcurrentHashMap;

public class CacheWithBloomFilter {
    private ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
    // 布隆过滤器,容量100000,误判率0.01
    private BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 100000, 0.01);

    // 模拟数据库查询
    private String loadFromDB(String key) {
        return null; // 模拟查询不到数据
    }

    public String getValue(String key) {
        // 先检查布隆过滤器是否可能存在
        if (!bloomFilter.mightContain(key)) {
            return null; // 数据不存在,直接返回
        }

        // 检查缓存
        String value = cache.get(key);
        if (value == null) {
            // 缓存中没有,查询数据库
            value = loadFromDB(key);
            if (value == null) {
                // 数据库中也没有,放入缓存空值
                cache.put(key, "NULL");
            } else {
                cache.put(key, value);
            }
        }
        return "NULL".equals(value) ? null : value; // 如果缓存的是NULL值,则返回null
    }
}

3.参数校验:在请求进入缓存层前,先进行数据校验,比如判断请求是否符合某种格式要求,避免无效的查询。


三 缓存雪崩

        缓存雪崩是指缓存中大量数据在同一时间失效,大量请求同时涌入数据库,导致数据库负载过重甚至宕机。这与缓存击穿的区别是,缓存击穿是针对单一热点数据,而缓存雪崩是大量缓存同时失效。

        例如大规模缓存设置了相同的过期时间,在缓存失效的瞬间,大量请求直接命中数据库,数据库瞬间压力过大,甚至可能导致服务不可用。

解决方案:

缓存数据的过期时间分散化:避免大量数据的缓存同时失效,可以对不同数据设置不同的过期时间,或者在过期时间的基础上加一个随机值,分散缓存的失效时间。

双重缓存机制:除了设置缓存的过期时间外,还可以引入一个备用缓存层,当第一层缓存失效时,先查询第二层缓存,减少对数据库的压力。

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

public class CacheWithRandomExpiration {
    private ConcurrentHashMap<String, CacheObject> cache = new ConcurrentHashMap<>();
    private Random random = new Random();

    // 模拟数据库查询
    private String loadFromDB(String key) {
        return "ValueFromDB";
    }

    public String getValue(String key) {
        CacheObject cacheObject = cache.get(key);
        if (cacheObject == null || cacheObject.isExpired()) {
            synchronized (this) {
                cacheObject = cache.get(key);
                if (cacheObject == null || cacheObject.isExpired()) {
                    String value = loadFromDB(key);
                    // 设置随机过期时间,比如加上 0-60秒的随机时间
                    long expirationTime = System.currentTimeMillis() + 1000 * 60 * 5 + random.nextInt(1000 * 60);
                    cacheObject = new CacheObject(value, expirationTime);
                    cache.put(key, cacheObject);
                }
            }
        }
        return cacheObject.getValue();
    }

    // 缓存对象类
    class CacheObject {
        private String value;
        private long expirationTime;

        public CacheObject(String value, long expirationTime) {
            this.value = value;
            this.expirationTime = expirationTime;
        }

        public String getValue() {
            return value;
        }

        public boolean isExpired() {
            return System.currentTimeMillis() > expirationTime;
        }
    }
}

限流和降级:在发生雪崩时,可以使用限流和服务降级机制,减少请求直接落到数据库上,保障数据库的稳定性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值