现象

某个热点数据设置了过期时间。

过期瞬间并发请求同时打到数据库,数据库压力突然升高。

简单模拟

伪代码如下:

public String getData(String key) {
    String value = redisTemplate.opsForValue().get(key);

    if (value == null) {
        value = queryFromDb(key);
        redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS);
    }

    return value;
}

当 key 过期时,多个线程同时进入 if 判断。

并发测试

简单用多线程模拟:

ExecutorService pool = Executors.newFixedThreadPool(20);

for (int i = 0; i < 20; i++) {
    pool.execute(() -> {
        getData("hot_key");
    });
}

在 key 失效瞬间,可以看到数据库查询被执行多次。

日志打印:

处理方式

使用互斥锁控制:

public String getData(String key) {
    String value = redisTemplate.opsForValue().get(key);

    if (value == null) {
        synchronized (this) {
            value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                value = queryFromDb(key);
                redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS);
            }
        }
    }

    return value;
}

或者使用 Redis 的 setnx 做分布式锁。

补充

缓存击穿一般出现在:

  • 热点数据
  • 过期时间一致
  • 高并发场景

和缓存穿透不同,击穿是 key 存在,只是刚好过期。