0
点赞
收藏
分享

微信扫一扫

Redis缓存击穿、缓存穿透、缓存雪崩

十里一走马 05-17 09:00 阅读 11

在分布式缓存系统中,Redis是一个常用的缓存解决方案,能够显著提高数据访问的性能。然而,在使用Redis缓存的过程中,可能会遇到以下几种常见的缓存问题:缓存击穿、缓存穿透和缓存雪崩。这三者虽然名字相似,但本质上是不同的缓存问题。理解这些问题,并且通过合适的策略来避免它们,对于提高系统的稳定性和性能至关重要。

1. 缓存击穿 (Cache Breakdown)

定义:缓存击穿是指在缓存中存储的某些数据过期后,多个请求同时访问该数据,导致这些请求绕过缓存直接访问数据库,造成数据库的压力骤增,甚至可能引发数据库崩溃。

场景:假设有一个热点数据(例如用户信息),该数据存储在缓存中。当缓存失效(例如过期)时,多个请求在同一时间尝试访问这个数据,由于缓存已经失效,它们会同时访问数据库,造成数据库压力增大。

解决方法:

  • 加锁机制:可以通过分布式锁,确保只有一个请求能够去查询数据库并加载数据到缓存,其他请求等待加载完成后直接从缓存读取。

Java 示例:

public class CacheBreakdownDemo {
    private static final String CACHE_KEY = "user_info";
    private static final String LOCK_KEY = "user_info_lock";

    // 模拟从数据库中获取数据
    public String getFromDb() {
        return "user_data_from_db";
    }

    // 获取缓存中的数据
    public String getFromCache() {
        // 假设从 Redis 获取缓存数据
        return null;  // 缓存失效,返回null
    }

    // 将数据设置到缓存中
    public void setToCache(String data) {
        // 假设把数据设置到 Redis 缓存中
    }

    public String getUserInfo() {
        String cachedData = getFromCache();
        if (cachedData != null) {
            return cachedData;  // 如果缓存有数据,直接返回
        }

        // 加锁,防止多个请求同时访问数据库
        synchronized (LOCK_KEY.intern()) {  // 使用分布式锁机制,保证同一时刻只有一个线程可以查询数据库
            cachedData = getFromCache();
            if (cachedData == null) {
                // 缓存中没有数据,去数据库查询
                cachedData = getFromDb();
                setToCache(cachedData);  // 查询到数据库后将数据缓存
            }
        }
        return cachedData;
    }
}

2. 缓存穿透 (Cache Penetration)

定义:缓存穿透是指查询的数据既不在缓存中,也不在数据库中,这种情况通常发生在查询条件不存在或者数据无效时。由于缓存没有这类数据,缓存系统直接把请求传递到数据库,数据库会进行查询,并且不会将无效数据缓存到Redis中。这样大量的无效请求直接打到数据库,造成数据库的压力增大。

场景:比如一个查询请求请求一个不存在的用户ID,而该ID既不在缓存中,也不存在于数据库中。这会导致每次请求都直接查询数据库,造成数据库不必要的负担。

解决方法:

  • 空值缓存:将查询结果为空的数据也缓存起来,避免相同的无效查询每次都访问数据库。
  • Bloom Filter:使用布隆过滤器在缓存层提前过滤掉不存在的数据,避免浪费数据库资源。

Java 示例:

public class CachePenetrationDemo {
    private static final String CACHE_KEY = "user_info";

    // 模拟从数据库获取数据
    public String getFromDb(String userId) {
        // 假设从数据库查询用户数据
        return null;  // 返回null表示没有此用户
    }

    // 获取缓存中的数据
    public String getFromCache(String userId) {
        // 假设从 Redis 获取缓存数据
        return null;  // 假设缓存未命中
    }

    // 将数据设置到缓存中
    public void setToCache(String userId, String data) {
        // 假设将数据保存到 Redis
    }

    public String getUserInfo(String userId) {
        // 先从缓存中获取数据
        String cachedData = getFromCache(userId);
        if (cachedData != null) {
            return cachedData;  // 如果缓存有数据,直接返回
        }

        // 缓存中没有数据,从数据库查询
        String dbData = getFromDb(userId);
        if (dbData == null) {
            // 如果数据库中也没有数据,缓存一个空值,防止下次再查
            setToCache(userId, "");  // 可以缓存空字符串或null标识
            return null;  // 返回空数据,表示不存在
        }

        // 如果数据库有数据,缓存结果
        setToCache(userId, dbData);
        return dbData;
    }
}

3. 缓存雪崩 (Cache Avalanche)

定义:缓存雪崩是指当缓存中的大量数据在同一时间失效,或者缓存服务器宕机时,所有请求都会直接访问数据库,导致数据库瞬间承受巨大的压力,可能造成数据库崩溃。

场景:如果缓存中存储的多个热点数据(例如商品信息、用户信息)在同一时间过期或者缓存服务器发生故障,所有的请求都会直接到数据库查询,从而引发数据库的压力暴增。

解决方法:

  • 缓存过期时间设置不同:避免所有缓存数据在同一时刻过期,可以设置缓存的过期时间有一定的随机性。
  • 使用备份缓存:如果主缓存不可用,使用备用缓存(如本地缓存)减少对数据库的依赖。
  • 降级处理:当缓存不可用时,进行降级处理,提供部分功能或者返回缓存中的默认数据。

Java 示例:

public class CacheAvalancheDemo {
    private static final String CACHE_KEY = "user_info";

    // 模拟从数据库获取数据
    public String getFromDb(String userId) {
        // 假设从数据库查询用户数据
        return "user_data_from_db";
    }

    // 获取缓存中的数据
    public String getFromCache(String userId) {
        // 假设从 Redis 获取缓存数据
        return null;  // 假设缓存失效
    }

    // 设置缓存
    public void setToCache(String userId, String data, long expireTime) {
        // 假设将数据设置到 Redis 缓存,expireTime 设置过期时间
    }

    // 模拟缓存雪崩的处理
    public String getUserInfo(String userId) {
        // 先从缓存中获取数据
        String cachedData = getFromCache(userId);
        if (cachedData != null) {
            return cachedData;
        }

        // 缓存过期,查询数据库
        String dbData = getFromDb(userId);
        if (dbData != null) {
            // 为了避免缓存雪崩,设置不同的过期时间
            setToCache(userId, dbData, System.currentTimeMillis() + (1000 * 60 * 60) + (Math.random() * 1000 * 60));
            return dbData;
        }

        return null;
    }
}

  • 缓存击穿:指缓存中某些数据过期时,多个请求同时访问,造成数据库压力增大。可以通过加锁来避免。
  • 缓存穿透:指查询的内容既不在缓存中,也不在数据库中,导致每次查询都打到数据库。可以通过缓存空值或者使用布隆过滤器来解决。
  • 缓存雪崩:指缓存中大量数据在同一时间失效,造成数据库压力暴增。可以通过设置不同的过期时间、使用备份缓存等方式来防止。

通过合理设计缓存机制,避免这三种问题,能够大大提高系统的稳定性和性能。

举报

相关推荐

0 条评论