1. 缓存穿透
问题描述:
缓存穿透是指查询一个在缓存和数据库中都不存在的数据。由于缓存不命中,每次请求都会直接访问数据库,导致数据库压力过大。
解决方案:
-
缓存空对象:
- 当缓存和数据库中都没有查询到数据时,将一个空对象(如
null或特殊标记)放入缓存,并设置一个较短的过期时间。 - 这样,下一次查询相同的 key 时,会直接命中缓存,避免访问数据库。
public Object getFromCache(String key) { Object value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } value = databaseQuery(key); if (value == null) { redisTemplate.opsForValue().set(key, "null", 60, TimeUnit.SECONDS); } else { redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS); } return value; } - 当缓存和数据库中都没有查询到数据时,将一个空对象(如
-
布隆过滤器:
- 使用布隆过滤器在缓存之前快速判断一个 key 是否存在。
- 布隆过滤器可以高效地判断一个元素是否可能存在于集合中,从而过滤掉大部分无效请求。
// 初始化布隆过滤器 BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 100000); public Object getFromCacheWithBloomFilter(String key) { if (!bloomFilter.mightContain(key)) { return null; } Object value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } value = databaseQuery(key); if (value == null) { redisTemplate.opsForValue().set(key, "null", 60, TimeUnit.SECONDS); } else { redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS); } return value; }2. 缓存击穿
问题描述:
缓存击穿是指一个热点数据在缓存过期的瞬间,有大量请求同时访问该数据,导致这些请求都直接访问数据库,造成数据库压力激增。解决方案:
-
互斥锁:
- 在缓存失效时,通过加锁来控制只有一个线程可以查询数据库并更新缓存,其他线程等待缓存更新完成后再获取数据。
public Object getFromCacheWithLock(String key) { Object value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } synchronized (this) { value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } value = databaseQuery(key); redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS); } return value; } -
提前预热:
- 对于已知的热点数据,可以在缓存过期前主动更新缓存,避免缓存失效导致的击穿。
// 定时任务提前预热缓存 @Scheduled(fixedRate = 3600000) public void refreshHotCache() { List<String> hotKeys = getHotKeys(); for (String key : hotKeys) { Object value = databaseQuery(key); redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS); } }3. 缓存雪崩
问题描述:
缓存雪崩是指在某一时刻大量缓存同时失效,导致大量请求直接访问数据库,造成数据库压力过大甚至崩溃。解决方案:
-
缓存过期时间设置随机化:
- 为不同的缓存设置不同的过期时间,避免大量缓存同时失效。
Random random = new Random(); int randomExpireTime = 3600 + random.nextInt(600); // 3600s 到 4200s 之间 redisTemplate.opsForValue().set(key, value, randomExpireTime, TimeUnit.SECONDS); -
双缓存机制:
- 使用双缓存机制,在旧缓存即将过期时,提前生成新缓存,确保缓存数据的连续性。
public Object getFromCacheWithDoubleCache(String key) { String oldKey = key + ":old"; String newKey = key + ":new"; Object value = redisTemplate.opsForValue().get(newKey); if (value != null) { return value; } value = redisTemplate.opsForValue().get(oldKey); if (value != null) { // 异步更新新缓存 asyncUpdateNewCache(key); return value; } value = databaseQuery(key); redisTemplate.opsForValue().set(newKey, value, 3600, TimeUnit.SECONDS); redisTemplate.opsForValue().set(oldKey, value, 7200, TimeUnit.SECONDS); return value; } private void asyncUpdateNewCache(String key) { new Thread(() -> { Object value = databaseQuery(key); redisTemplate.opsForValue().set(key + ":new", value, 3600, TimeUnit.SECONDS); }).start(); } -
限流降级:
- 在缓存失效时,限流部分请求,或者直接返回默认值,保护数据库不被压垮。
public Object getFromCacheWithRateLimit(String key) { if (rateLimiter.tryAcquire()) { Object value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } value = databaseQuery(key); redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS); return value; } else { // 返回默认值或降级处理 return getDefaultResponse(); } }总结
- 缓存穿透:使用缓存空对象和布隆过滤器来防止无效请求穿透缓存。
- 缓存击穿:使用互斥锁和提前预热来避免热点缓存失效导致的击穿。
- 缓存雪崩:通过随机化过期时间、双缓存机制和限流降级来防止大量缓存同时失效引发的雪崩。