Redis(81)Redis的缓存雪崩是什么?

缓存雪崩的概念

缓存雪崩(Cache Avalanche)是指在某一时间段内,缓存中的大量数据同时过期,或者由于缓存服务器宕机导致大量请求直接打到数据库,导致数据库瞬时压力剧增,甚至可能导致数据库崩溃。

解决缓存雪崩的方法

为了解决缓存雪崩问题,可以采取以下几种策略:

  1. 缓存数据的过期时间设置为随机值:避免在同一时间大量缓存数据同时失效。
  2. 加锁或队列:在缓存失效时,通过机制控制对数据库的访问,避免大量请求同时打到数据库。
  3. 双写策略:更新缓存的同时也更新数据库,保证数据的一致性。
  4. 数据预热:在系统启动时,预先将一些热点数据加载到缓存中,防止缓存雪崩。

解决方法及详细代码示例

以下是几种具体的解决方法及示例代码:

1. 缓存数据的过期时间设置为随机值

通过设置随机的过期时间,可以避免大量缓存数据在同一时间失效。

示例代码:

java 复制代码
import redis.clients.jedis.Jedis;

public class CacheAvalancheExample {
    private Jedis jedis;

    public CacheAvalancheExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public void setCachedData(String key, String value, int baseExpTime) {
        int expTime = baseExpTime + (int)(Math.random() * baseExpTime); // 随机过期时间
        jedis.setex(key, expTime, value);
    }

    public String getCachedData(String key) {
        return jedis.get(key);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        CacheAvalancheExample cache = new CacheAvalancheExample(jedis);

        String key = "dataKey";
        String value = "dataValue";
        int baseExpTime = 3600; // 基础过期时间 1 小时

        cache.setCachedData(key, value, baseExpTime);

        String cachedValue = cache.getCachedData(key);
        System.out.println("Cached Value: " + cachedValue);

        jedis.close();
    }
}

2. 加锁或队列

通过加锁或者队列的方式,控制缓存失效时对数据库的访问,避免大量请求同时打到数据库。

示例代码:

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

public class CacheAvalancheWithLockExample {
    private Jedis jedis;

    public CacheAvalancheWithLockExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public String getCachedData(String key, DataProvider provider, int cacheTime) {
        String value = jedis.get(key);
        if (value != null) {
            return value;
        }

        String lockKey = key + ":lock";
        String requestId = String.valueOf(Thread.currentThread().getId());

        // 尝试加锁
        boolean locked = tryGetLock(lockKey, requestId, 30000); // 锁定30秒
        if (locked) {
            try {
                value = provider.getData();
                if (value != null) {
                    jedis.setex(key, cacheTime, value);
                }
            } finally {
                releaseLock(lockKey, requestId);
            }
        } else {
            // 等待一段时间后重试
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCachedData(key, provider, cacheTime);
        }
        return value;
    }

    private boolean tryGetLock(String lockKey, String requestId, int expireTime) {
        SetParams params = new SetParams().nx().px(expireTime);
        String result = jedis.set(lockKey, requestId, params);
        return "OK".equals(result);
    }

    private void releaseLock(String lockKey, String requestId) {
        if (requestId.equals(jedis.get(lockKey))) {
            jedis.del(lockKey);
        }
    }

    public interface DataProvider {
        String getData();
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        CacheAvalancheWithLockExample cache = new CacheAvalancheWithLockExample(jedis);

        String key = "dataKey";
        int cacheTime = 3600; // 缓存 1 小时

        String value = cache.getCachedData(key, () -> {
            // 模拟数据库查询
            return "dataValue";
        }, cacheTime);

        System.out.println("Cached Value: " + value);

        jedis.close();
    }
}

3. 双写策略

更新数据库的同时也更新缓存,可以保证缓存的一致性,减小缓存失效的几率。

示例代码:

java 复制代码
import redis.clients.jedis.Jedis;

public class CacheAvalancheWithDoubleWriteExample {
    private Jedis jedis;

    public CacheAvalancheWithDoubleWriteExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public void updateData(String key, String value, int cacheTime) {
        // 更新数据库
        updateDatabase(key, value);

        // 更新缓存
        jedis.setex(key, cacheTime, value);
    }

    private void updateDatabase(String key, String value) {
        // 模拟数据库更新操作
        System.out.println("Database updated: " + key + " = " + value);
    }

    public String getCachedData(String key) {
        return jedis.get(key);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        CacheAvalancheWithDoubleWriteExample cache = new CacheAvalancheWithDoubleWriteExample(jedis);

        String key = "dataKey";
        String value = "dataValue";
        int cacheTime = 3600; // 缓存 1 小时

        cache.updateData(key, value, cacheTime);

        String cachedValue = cache.getCachedData(key);
        System.out.println("Cached Value: " + cachedValue);

        jedis.close();
    }
}

4. 数据预热

在系统启动时预先将一些热点数据加载到缓存中,可以有效防止缓存雪崩。

示例代码:

java 复制代码
import redis.clients.jedis.Jedis;

public class CacheWarmUpExample {
    private Jedis jedis;

    public CacheWarmUpExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public void warmUpCache() {
        // 预先加载热点数据到缓存中
        String key = "hotKey";
        String value = "hotValue";
        int cacheTime = 3600; // 缓存 1 小时
        jedis.setex(key, cacheTime, value);

        System.out.println("Cache warmed up: " + key + " = " + value);
    }

    public String getCachedData(String key) {
        return jedis.get(key);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        CacheWarmUpExample cache = new CacheWarmUpExample(jedis);

        cache.warmUpCache();

        String key = "hotKey";
        String cachedValue = cache.getCachedData(key);
        System.out.println("Cached Value: " + cachedValue);

        jedis.close();
    }
}

总结

通过以上示例代码,您可以分别采用随机过期时间、加锁或队列、双写策略和数据预热等方法来解决Redis的缓存雪崩问题。合理使用这些方法,可以有效地减少对数据库的瞬时压力,提高系统的稳健性和可用性。

相关推荐
刚子编程3 小时前
我用 ASP.NET Core 做了个水稻病虫害检查系统
后端·asp.net
谢亮_vipxieliang3 小时前
用 PHP 构建轻量级 REST API:从路由到鉴权的完整实践
开发语言·后端·php
码事漫谈6 小时前
三步改掉 AI 味,附可直接复制的去 AI 味提示词
后端
IT_陈寒6 小时前
Redis卡顿的锅,这次真不是大key的错
前端·人工智能·后端
可乐鸡翅yeah_6 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放
程序猿_极客6 小时前
【免费】分享一套优质的基于SpringBoot的服装商城管理系统的设计与实现(带可视化图表、协同过滤功能),源码+文档+视频详解(讲解)
java·spring boot·后端·服装商城管理系统
明月_清风7 小时前
面对陌生的 GitHub 项目无从下手?这 4 个网站帮你快速读懂源码
前端·后端·github
bug菌7 小时前
🤔同事突然问我:Spring的注解 @Component 和 @Service 有何不同?
java·spring boot·后端
leobertlan7 小时前
痛苦系列 | DSP-02 频域切片:DTFT与DFT的探索
android·后端
加我攻城狮7 小时前
当大模型把「景区」定位到附近公共厕所:我的 AI 旅行小程序踩坑实录
后端·微信