Redis 缓存击穿介绍

缓存击穿(Cache Breakdown)是指在缓存中某个数据存在,但是由于某些原因(如缓存过期、缓存失效),导致大量请求直接访问数据库。这种情况通常发生在缓存的某个数据点在高并发场景下,数据失效或被删除时,多个请求同时击中缓存失效的区域,导致数据库承受大量的请求压力。

缓存击穿的原因

  1. 缓存过期:缓存中的某些数据由于设置了过期时间而过期了。
  2. 缓存删除:缓存中的某些数据被手动删除或清除。
  3. 高并发:在高并发请求的情况下,多个请求同时访问缓存失效的数据,导致请求并发打到数据库上。

缓存击穿的影响

  • 数据库负担过重:多个请求直接访问数据库,数据库负载瞬间增加,可能会引发性能瓶颈,甚至导致数据库宕机。
  • 系统响应延迟:高并发的数据库请求会导致响应延迟,影响系统整体性能。
  • 资源浪费:无效的并发请求占用了计算资源和网络带宽。

如何解决缓存击穿

  1. 互斥锁:通过加锁(互斥锁)确保同一时间只有一个请求可以去访问数据库,其他请求等待缓存更新后再访问缓存。
  2. 使用双重检查:结合双重检查锁定模式,避免多个线程同时查询数据库。
  3. 设置合适的缓存过期时间:避免过多的缓存失效,合理设置缓存的过期时间,避免单一数据点的缓存失效对系统产生压力。
  4. 预热缓存:在缓存失效后,预先加载一部分热点数据到缓存,避免数据库被突发请求压垮。

1. 使用互斥锁防止缓存击穿

通过使用 Redis 的分布式锁(如 SETNXRedisson)机制,保证在缓存失效时,只有一个请求能够去查询数据库并更新缓存,其他请求等待缓存更新完成后再从缓存获取数据。

代码实现:

使用 Redisson 库来实现分布式锁。

  1. 安装依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.1</version>
</dependency>

代码实现

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.RBucket;
import org.redisson.config.Config;
import org.redisson.Redisson;

public class CacheBreakdownExample {

    private static RedissonClient redisson;

    public static void main(String[] args) {
        // 配置 Redisson 客户端连接 Redis
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        redisson = Redisson.create(config);

        // 模拟并发访问数据库的操作
        String key = "user:1001";  // 假设查询用户 ID 为 1001
        String value = getUserFromCache(key);

        if (value == null) {
            // 缓存失效,查询数据库
            value = getUserFromDatabase(key);
            // 通过加锁确保只有一个线程可以更新缓存
            RLock lock = redisson.getLock(key + ":lock");

            lock.lock();
            try {
                // 如果缓存依然未命中,则更新缓存
                if (getUserFromCache(key) == null) {
                    setUserToCache(key, value);
                }
            } finally {
                lock.unlock();
            }
        }

        System.out.println("Fetched value: " + value);
        redisson.shutdown();
    }

    // 从缓存获取用户数据
    public static String getUserFromCache(String key) {
        RBucket<String> bucket = redisson.getBucket(key);
        return bucket.get();
    }

    // 设置用户数据到缓存
    public static void setUserToCache(String key, String value) {
        RBucket<String> bucket = redisson.getBucket(key);
        bucket.set(value);
    }

    // 模拟查询数据库
    public static String getUserFromDatabase(String key) {
        // 假设数据库查询结果
        return "User data for " + key;
    }
}

  1. 加锁机制:使用 Redis 锁(RLock)确保同一时刻只有一个线程去访问数据库并更新缓存,避免缓存失效后高并发访问数据库的情况。
  2. 缓存更新:当缓存失效时,通过 lock.lock() 获取锁,确保只有一个线程能够执行数据库查询和缓存更新,其他请求会等待锁释放后访问缓存。
  3. Redis 操作:使用 Redisson 的 RBucket 存储和获取数据。

通过这种方式,只有一个请求可以查询数据库并更新缓存,其他请求在缓存更新之前等待,避免了缓存击穿。

2. 双重检查锁定模式

双重检查锁定模式是另一种常见的防止缓存击穿的方式,确保同一时间只有一个线程能够查询数据库并更新缓存,减少锁的粒度。

代码实现:
import redis.clients.jedis.Jedis;

public class CacheBreakdownDoubleCheck {

    private static Jedis jedis = new Jedis("localhost", 6379);

    public static void main(String[] args) {
        String key = "user:1002";  // 假设查询用户 ID 为 1002

        // 第一重检查:从缓存获取数据
        String value = getFromCache(key);

        if (value == null) {
            // 如果缓存为空,进入第二重检查
            synchronized (CacheBreakdownDoubleCheck.class) {
                value = getFromCache(key);
                if (value == null) {
                    // 如果缓存依然为空,则查询数据库并更新缓存
                    value = getFromDatabase(key);
                    setToCache(key, value);
                }
            }
        }

        System.out.println("Fetched value: " + value);
    }

    // 从缓存获取数据
    public static String getFromCache(String key) {
        return jedis.get(key);
    }

    // 设置数据到缓存
    public static void setToCache(String key, String value) {
        jedis.setex(key, 3600, value);  // 设置缓存过期时间为1小时
    }

    // 模拟查询数据库
    public static String getFromDatabase(String key) {
        // 假设数据库查询返回数据
        return "User data for " + key;
    }
}
  1. 双重检查:第一次检查缓存,如果缓存中没有数据,则进入同步块。
  2. 同步块:进入同步块后,再次检查缓存,防止多个线程同时查询数据库。
  3. 缓存更新:如果缓存仍然为空,则查询数据库并更新缓存。

这种方法通过减少加锁的时间窗口,提升了系统的性能。

Redis的碎碎念 文章被收录于专栏

Redis面试中的碎碎念

全部评论

相关推荐

评论
2
2
分享

创作者周榜

更多
牛客网
牛客企业服务