Redis(84)如何解决Redis的缓存击穿问题?

缓存击穿的概念

缓存击穿(Cache Breakdown)指的是在某一个热点缓存数据过期的瞬间,有大量并发请求同时访问这个数据,而该数据在缓存中不存在,因此所有的请求都打到数据库上,导致数据库压力过大,可能引起系统性能问题。

解决缓存击穿的方法

为了解决缓存击穿问题,可以采取以下策略:

  1. 互斥锁(Mutex):在缓存失效时,只有一个线程去加载数据,其他线程等待。
  2. 永不过期:热点数据的缓存永不过期,只在数据更新时主动去更新缓存。
  3. 预加载:在缓存即将过期之前,提前加载数据到缓存。

以下是这几种解决方法的详细代码示例:

1. 互斥锁(Mutex)

通过加锁的方式,控制缓存失效时只有一个线程去加载数据,其他线程等待,从而避免大量请求同时打到数据库。

示例代码:

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

public class CacheBreakdownWithLockExample {
    private Jedis jedis;

    public CacheBreakdownWithLockExample(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);
        CacheBreakdownWithLockExample cache = new CacheBreakdownWithLockExample(jedis);

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

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

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

        jedis.close();
    }
}

2. 永不过期

对于热点数据,设置其缓存为永不过期,只在数据更新时主动去更新缓存。

示例代码:

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

public class CacheBreakdownWithNoExpireExample {
    private Jedis jedis;

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

    public void setCachedData(String key, String value) {
        jedis.set(key, value); // 设置为永不过期
    }

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

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

        String key = "dataKey";
        String value = "dataValue";

        // 设置缓存数据
        cache.setCachedData(key, value);

        // 获取缓存数据
        String cachedValue = cache.getCachedData(key);
        System.out.println("Cached Value: " + cachedValue);

        jedis.close();
    }
}

3. 预加载

在缓存即将过期之前,提前加载数据到缓存中,防止缓存失效带来的问题。

示例代码:

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

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class CacheBreakdownWithPreloadExample {
    private Jedis jedis;

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

    public void setCachedData(String key, String value, int cacheTime) {
        jedis.setex(key, cacheTime, value);
    }

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

    public void preloadCache(String key, DataProvider provider, int cacheTime, int preloadTime) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            String value = provider.getData();
            if (value != null) {
                jedis.setex(key, cacheTime, value);
            }
        }, 0, preloadTime, TimeUnit.SECONDS);
    }

    public interface DataProvider {
        String getData();
    }

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

        String key = "dataKey";
        int cacheTime = 3600; // 缓存 1 小时
        int preloadTime = 3500; // 预加载时间 3500 秒

        // 模拟数据预加载
        cache.preloadCache(key, () -> {
            // 模拟数据库查询
            return "dataValue";
        }, cacheTime, preloadTime);

        // 获取缓存数据
        String cachedValue = cache.getCachedData(key);
        System.out.println("Cached Value: " + cachedValue);

        jedis.close();
    }
}

总结

通过以上示例代码,您可以分别采用互斥锁、永不过期和预加载等方法来解决Redis的缓存击穿问题。合理使用这些方法,可以有效避免热点数据失效时对数据库的瞬时压力,提高系统的稳健性和可用性。

相关推荐
xbgRS2 小时前
springboot的自动装配
java·spring boot
晚染烟8 小时前
每日八股day17
redis
嘻哈∠※9 小时前
0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
java·spring boot·后端
m0_3807438710 小时前
给大模型调用加一层本地缓存,让重复请求直接命中磁盘
缓存
白狐_79810 小时前
408数据结构第8章:排序②——性质对比秒杀、场景选择与外部排序
java·数据结构·算法
zander25811 小时前
LeetCode 84:柱状图中的最大矩形——单调栈如何确定左右边界
java·数据结构·算法
码匠许师傅11 小时前
【C++ 面试真题】聊聊 C++ 的拷贝构造与拷贝赋值
java·c++·面试
FakeOccupational12 小时前
【电路笔记 STM32】Cortex-M7 内核上的数据缓存(D-Cache)结构+MPU+DMA&Cache+STM32CubeMX配置
笔记·stm32·缓存
极创信息13 小时前
系统安全隐患全方位排查方案:标准化渗透测试全流程
java·opencv·struts·数据挖掘·eclipse·语音识别·hibernate
汉字萌萌哒13 小时前
2019CCF-CSP入门级C++试题解析
java·开发语言·c++