Bootstrap

Reids缓存穿透、缓存雪崩和缓存击穿

Redis缓存中常见的三个问题:缓存穿透缓存雪崩缓存击穿。这些问题在使用Redis作为缓存时经常遇到,但通过合理的策略可以有效解决。我会用简单易懂的方式来讲解,帮助你理解这些问题的原理和解决方案。


1. 缓存穿透

1.1 什么是缓存穿透?

缓存穿透是指查询一个数据库中不存在的数据,由于缓存不会保存这样的数据,每次查询都会直接穿透到数据库,从而增加数据库的压力。

1.2 为什么会出现缓存穿透?

  • 请求非法数据:用户请求了一个不存在的数据。

  • 缓存未命中:缓存中没有保存这样的数据,每次查询都会直接访问数据库。

1.3 如何解决缓存穿透?

  1. 接口层面校验

    • 在接口层面验证请求的合法性,避免非法请求直接穿透到数据库。

    • 例如,检查请求的ID是否合法,是否符合业务逻辑。

  2. 缓存空对象

    • 对于查询不存在的数据,将空对象或默认值缓存一段时间,避免每次查询都穿透到数据库。

    • 例如,缓存一个空的JSON对象或null值。

  3. 布隆过滤器

    • 使用布隆过滤器(Bloom Filter)预先存储可能存在的数据ID,查询时先检查布隆过滤器。

    • 如果布隆过滤器判断数据不存在,则直接返回,避免查询数据库。

示例代码

const cache = require('some-cache-library'); // 假设的缓存库
const database = require('some-database-library'); // 假设的数据库库

async function getData(id) {
    // 检查缓存
    let data = cache.get(id);
    if (data) {
        return data;
    }

    // 查询数据库
    data = await database.query(id);
    if (data) {
        // 缓存数据
        cache.set(id, data, 3600); // 缓存1小时
    } else {
        // 缓存空对象
        cache.set(id, null, 60); // 缓存1分钟
    }
    return data;
}

2. 缓存雪崩

2.1 什么是缓存雪崩?

缓存雪崩是指在缓存层(如Redis)中的所有缓存数据同时过期,导致大量请求直接穿透到数据库,从而引发数据库压力剧增甚至崩溃。

2.2 为什么会出现缓存雪崩?

  • 缓存过期时间一致:所有缓存数据的过期时间相同,导致同时过期。

  • 缓存层宕机:缓存层(如Redis)宕机,所有请求直接穿透到数据库。

2.3 如何解决缓存雪崩?

  1. 设置不同的过期时间

    • 为缓存数据设置不同的过期时间,避免同时过期。

    • 例如,使用随机的过期时间范围。

  2. 使用本地缓存

    • 在应用层使用本地缓存(如Guava Cache),作为第一级缓存,减轻Redis的压力。

  3. 使用Redis集群

    • 使用Redis集群,避免单点故障。

  4. 预热缓存

    • 在系统启动时,预先加载热点数据到缓存中。

  5. 限流和降级

    • 在接口层面使用限流和降级策略,避免过多请求同时访问数据库。

示例代码

const cache = require('some-cache-library'); // 假设的缓存库
const database = require('some-database-library'); // 假设的数据库库

async function getData(id) {
    // 检查缓存
    let data = cache.get(id);
    if (data) {
        return data;
    }

    // 查询数据库
    data = await database.query(id);
    if (data) {
        // 缓存数据,设置随机过期时间
        const randomExpire = 3600 + Math.floor(Math.random() * 3600); // 1-2小时
        cache.set(id, data, randomExpire);
    }
    return data;
}

3. 缓存击穿

3.1 什么是缓存击穿?

缓存击穿是指一个热点数据在缓存过期时,大量请求同时访问数据库,导致数据库压力剧增。

3.2 为什么会出现缓存击穿?

  • 热点数据过期:热点数据的缓存过期,导致大量请求同时访问数据库。

  • 高并发请求:在缓存过期时,大量并发请求同时到达。

3.3 如何解决缓存击穿?

  1. 使用互斥锁

    • 在缓存过期时,使用互斥锁(如Redis的SETNX命令)确保只有一个请求去查询数据库,其他请求等待。

    • 例如,使用SETNX命令设置一个锁,只有第一个请求能够查询数据库并更新缓存。

  2. 双层缓存

    • 使用两层缓存,第一层缓存(如本地缓存)过期时间稍短,第二层缓存(如Redis)过期时间稍长。

    • 第一层缓存过期时,第二层缓存仍然可用,避免直接穿透到数据库。

  3. 预热缓存

    • 在系统启动时,预先加载热点数据到缓存中,避免缓存过期时的高并发请求。

示例代码

const cache = require('some-cache-library'); // 假设的缓存库
const database = require('some-database-library'); // 假设的数据库库

async function getData(id) {
    // 检查缓存
    let data = cache.get(id);
    if (data) {
        return data;
    }

    // 设置互斥锁
    const lockKey = `lock:${id}`;
    if (cache.set(lockKey, 'locked', 10)) { // 设置锁,过期时间10秒
        try {
            // 查询数据库
            data = await database.query(id);
            if (data) {
                // 更新缓存
                cache.set(id, data, 3600);
            }
        } finally {
            // 释放锁
            cache.del(lockKey);
        }
    } else {
        // 等待其他请求更新缓存
        await new Promise(resolve => setTimeout(resolve, 1000));
        data = cache.get(id);
    }
    return data;
}

4. 总结

  • 缓存穿透:查询不存在的数据,导致每次查询都穿透到数据库。

    • 解决方案:接口层面校验、缓存空对象、使用布隆过滤器。

  • 缓存雪崩:所有缓存数据同时过期,导致大量请求穿透到数据库。

    • 解决方案:设置不同的过期时间、使用本地缓存、使用Redis集群、预热缓存、限流和降级。

  • 缓存击穿:热点数据过期时,大量请求同时访问数据库。

    • 解决方案:使用互斥锁、双层缓存、预热缓存。

通过合理的策略和配置,可以有效解决Redis缓存中的这些问题,提高系统的稳定性和性能。

;