Redis中的缓存雪崩与缓存穿透
缓存雪崩 和 缓存穿透 是两个常见的缓存问题,它们会直接影响系统性能,尤其是在高并发的场景下。
1. 缓存雪崩(Cache Avalanche)
定义:缓存雪崩是指大量缓存数据在同一时刻失效,导致大量请求直接访问数据库,造成数据库的压力剧增,可能导致系统崩溃或响应时间大幅延迟。
原因:
- 如果缓存数据的过期时间设置相同,所有缓存数据可能会在同一时刻失效,导致大量的请求同时穿透缓存访问数据库,给数据库带来极大压力。
解决方案:
- 设置不同的缓存过期时间:避免所有数据在同一时刻过期,可以通过设置缓存的过期时间随机化来减少过期数据集中失效的风险。
- 使用互斥锁(Mutex):防止多个请求在缓存失效时同时访问数据库。可以通过互斥锁机制确保只有一个请求访问数据库,其他请求可以等待缓存的重新加载。
- 预热缓存:在系统启动时或数据库数据变动时,提前加载缓存,避免缓存为空导致大量请求直接访问数据库。
2. 缓存穿透(Cache Penetration)
定义:缓存穿透是指查询的缓存数据和数据库中都不存在的数据,导致每次查询都需要访问数据库,从而造成数据库负载增加。
原因:
- 请求查询的数据不在缓存中,也不在数据库中,导致每次都需要访问数据库。
解决方案:
- 缓存空值:当查询结果为空时,将空值缓存一段时间,避免后续请求直接穿透缓存访问数据库。
- 请求参数校验:对请求参数进行校验,防止恶意请求访问不存在的数据。
- 布隆过滤器:通过布隆过滤器提前拦截不存在的数据请求,避免对不存在的数据访问数据库。
3. Java中使用RedisTemplate的代码示例
在Spring中,RedisTemplate
是一个非常常用的工具,它能够方便地与Redis进行交互。下面我们会给出 缓存雪崩 和 缓存穿透 的相关解决方案代码。
1. 缓存雪崩的解决方案
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class CacheAvalancheService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public String getData(String key) {
// 1. 检查缓存
String cachedData = redisTemplate.opsForValue().get(key);
if (cachedData == null) { // 缓存未命中
synchronized (CacheAvalancheService.class) {
cachedData = redisTemplate.opsForValue().get(key); // 二次检查缓存
if (cachedData == null) {
// 2. 查询数据库
cachedData = getDataFromDatabase(key);
// 3. 设置随机过期时间,避免缓存雪崩
long expiration = 5 + (long)(Math.random() * 5); // 过期时间5~10秒
redisTemplate.opsForValue().set(key, cachedData, expiration, TimeUnit.SECONDS);
}
}
}
return cachedData;
}
private String getDataFromDatabase(String key) {
// 模拟从数据库获取数据
return "data-from-db"; // 实际中可以是数据库查询操作
}
}
解决方案说明:
- 缓存失效时,加锁 :通过
synchronized
确保只有一个线程可以访问数据库,其他线程等待。 - 设置随机过期时间 :通过
Math.random()
设置不同的过期时间,避免大量缓存数据同时失效。
2. 缓存穿透的解决方案
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class CachePenetrationService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public String getData(String key) {
// 1. 检查缓存
String cachedData = redisTemplate.opsForValue().get(key);
if (cachedData == null) { // 如果缓存中没有数据
// 2. 查询数据库
String dataFromDb = getDataFromDatabase(key);
if (dataFromDb == null) { // 如果数据库中也没有数据
// 3. 缓存空值
redisTemplate.opsForValue().set(key, "", 60, TimeUnit.SECONDS); // 空值缓存,避免频繁查询
return null; // 返回空值
} else {
// 4. 将数据库数据缓存起来
redisTemplate.opsForValue().set(key, dataFromDb, 60, TimeUnit.SECONDS); // 设置有效数据缓存
return dataFromDb;
}
}
return cachedData; // 缓存命中
}
private String getDataFromDatabase(String key) {
// 模拟数据库查询
return null; // 假设数据库查询不到数据
}
}
解决方案说明:
- 缓存空值 :当数据库查询到空数据时,将空值(如
""
或"null"
)缓存一段时间,避免相同请求频繁查询数据库。 - 数据库查询:如果缓存没有数据,直接查询数据库并将结果缓存。如果数据库查询不到数据,则缓存空值。
4. 总结与优化
- 缓存雪崩 :
- 解决方案:设置不同的缓存过期时间、使用互斥锁保证只有一个请求查询数据库、预热缓存。
- 效果:避免大量缓存同时失效导致的数据库压力。
- 缓存穿透 :
- 解决方案:缓存空值、参数校验、布隆过滤器。
- 效果:避免频繁查询数据库的无效请求,减少数据库负担。
通过这些策略,可以有效地避免缓存雪崩和缓存穿透问题,提升系统的性能和稳定性。