在高并发系统里,Redis 经常被放在数据库前面,当作一层缓存。

正常情况下,请求流程是这样的:

这样做的好处很明显:Redis 快,可以帮 MySQL 分担大量压力。

但是缓存不是万能的,用不好也会出问题。常见的 Redis 缓存问题主要有:

1. 缓存穿透
2. 缓存击穿
3. 缓存雪崩
4. Big Key

一、缓存穿透

1. 什么是缓存穿透?

缓存穿透指的是:

查询的数据在 Redis 中不存在,在数据库中也不存在,导致每次请求都会打到数据库。

比如用户请求:

/product/999999

但是商品 999999 根本不存在。

流程就变成:

那么这些请求都会绕过 Redis,直接打到数据库。

解决方案:

1.参数校验

2.缓存空对象

3.布隆过滤器(布隆过滤器适合数据量大、非法请求多的场景。)

二、缓存击穿

缓存击穿指的是:某一个热点 key 突然失效,大量请求同时打到数据库。

注意重点是:一个热点 key

比如一个爆款商品:

这个商品访问量非常高,平时大家都从 Redis 查。

但是某一刻,这个 key 刚好过期了。

这时候大量请求同时进来:

流程就变成

解决方案:

1. 互斥锁:缓存失效后,只允许一个线程去查数据库并重建缓存,其他线程等待。

示例代码:

public Product getProductById(Long id) {
    String key = "product:" + id;
    String lockKey = "lock:product:" + id;

    String json = redisTemplate.opsForValue().get(key);

    if (json != null && !"NULL".equals(json)) {
        return JSON.parseObject(json, Product.class);
    }

    Boolean lock = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

    if (Boolean.TRUE.equals(lock)) {
        try {
            // 双重检查,防止其他线程已经重建缓存
            json = redisTemplate.opsForValue().get(key);
            if (json != null && !"NULL".equals(json)) {
                return JSON.parseObject(json, Product.class);
            }

            Product product = productMapper.selectById(id);

            if (product == null) {
                redisTemplate.opsForValue().set(key, "NULL", 2, TimeUnit.MINUTES);
                return null;
            }

            redisTemplate.opsForValue().set(
                    key,
                    JSON.toJSONString(product),
                    30,
                    TimeUnit.MINUTES
            );

            return product;
        } finally {
            redisTemplate.delete(lockKey);
        }
    }

    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }

    String retryJson = redisTemplate.opsForValue().get(key);
    if (retryJson != null && !"NULL".equals(retryJson)) {
        return JSON.parseObject(retryJson, Product.class);
    }

    return null;
}

这里的锁本质上就是 Redis 的:

SET lock:product:1001 1 NX EX 10

这样可以避免程序宕机后锁一直不释放。

2.热点key不过期(每次查询重置超时时间)。

三、缓存雪崩

1.什么是缓存雪崩?

缓存雪崩指的是:

大量缓存 key 在同一时间失效,或者 Redis 整体宕机,导致大量请求瞬间打到数据库。

比如我们批量缓存了 10 万个商品,并且统一设置 30 分钟过期:

如果这些 key 是同一时间写入的,那它们很可能同一时间过期。

30 分钟后:

解决方案:

1. 过期时间加随机值

2.缓存预热:系统启动或者活动开始前,提前把热点数据加载到 Redis。

四、Big Key 问题

Big Key 不是说 key 的名字很长,而是说:

某个 key 对应的 value 太大,或者集合类型中元素太多。

比如:

这些都可能是 Big Key。

2. Big Key 的危害

Redis 核心命令执行主要是单线程。

如果对 Big Key 执行:

redis 会一次性处理大量数据,其他请求只能排队。

结果就是:Redis 卡顿、接口变慢、请求超时、CPU 飙高。

3.如何发现 Big Key?

使用 redis-cli --bigkeys

这个命令可以扫描出各类型中比较大的 key。生产环境建议低峰期执行。

总结:

穿透是查不存在的数据,击穿是一个热点 key 失效,雪崩是一大片 key 同时失效,Big Key 是一个 key 太胖,热点 Key 是一个 key 太火。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐