缓存架构设计
缓存架构设计
多级缓存架构
多级缓存采用本地缓存 + 分布式缓存 + 数据库的分层策略,逐层过滤请求,减轻后端压力。
请求 → 本地缓存(L1) → Redis(L2) → 数据库(L3)
@Service
public class MultiLevelCacheService {
private final Cache<String, Object> localCache;
private final RedisTemplate<String, Object> redisTemplate;
private final DataSource dataSource;
public Object get(String key) {
// L1: 本地缓存
Object value = localCache.getIfPresent(key);
if (value != null) return value;
// L2: Redis分布式缓存
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
return value;
}
// L3: 数据库回源
value = dataSource.query(key);
if (value != null) {
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
localCache.put(key, value);
}
return value;
}
}
缓存穿透
缓存穿透指查询不存在的数据,每次都穿透到数据库。解决方案包括布隆过滤器和缓存空值。
@Component
public class BloomFilterCache {
private final BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8),
1_000_000, 0.01
);
public Object get(String key) {
if (!bloomFilter.mightContain(key)) {
return null; // 布隆过滤器拦截
}
Object value = cache.get(key);
if (value == null) {
value = database.query(key);
if (value == null) {
cache.put(key, NULL_PLACEHOLDER, 5, TimeUnit.MINUTES);
}
}
return value;
}
}
缓存击穿
缓存击穿是热点Key过期瞬间大量请求直达数据库。通过互斥锁或永不过期+异步刷新解决。
public Object getWithMutex(String key) {
Object value = cache.get(key);
if (value != null) return value;
String lockKey = "lock:" + key;
if (redis.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
try {
value = database.query(key);
cache.put(key, value, 30, TimeUnit.MINUTES);
} finally {
redis.delete(lockKey);
}
} else {
Thread.sleep(50);
return getWithMutex(key);
}
return value;
}
缓存雪崩
缓存雪崩是大量Key同时过期或Redis宕机导致请求涌入数据库。采用随机过期时间和集群高可用策略。
public void setWithRandomTTL(String key, Object value, int baseTTL) {
int randomTTL = baseTTL + ThreadLocalRandom.current().nextInt(300);
redisTemplate.opsForValue().set(key, value, randomTTL, TimeUnit.SECONDS);
}
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSentinelServers()
.setMasterName("mymaster")
.addSentinelAddress("redis1:26379", "redis2:26379", "redis3:26379");
return Redisson.create(config);
}