缓存雪崩的概念
缓存雪崩(Cache Avalanche)是指在某一时间段内,缓存中的大量数据同时过期,或者由于缓存服务器宕机导致大量请求直接打到数据库,导致数据库瞬时压力剧增,甚至可能导致数据库崩溃。
解决缓存雪崩的方法
为了解决缓存雪崩问题,可以采取以下几种策略:
- 缓存数据的过期时间设置为随机值:避免在同一时间大量缓存数据同时失效。
- 加锁或队列:在缓存失效时,通过机制控制对数据库的访问,避免大量请求同时打到数据库。
- 双写策略:更新缓存的同时也更新数据库,保证数据的一致性。
- 数据预热:在系统启动时,预先将一些热点数据加载到缓存中,防止缓存雪崩。
解决方法及详细代码示例
以下是几种具体的解决方法及示例代码:
1. 缓存数据的过期时间设置为随机值
通过设置随机的过期时间,可以避免大量缓存数据在同一时间失效。
示例代码:
1import redis.clients.jedis.Jedis; 2 3public class CacheAvalancheExample { 4 private Jedis jedis; 5 6 public CacheAvalancheExample(Jedis jedis) { 7 this.jedis = jedis; 8 } 9 10 public void setCachedData(String key, String value, int baseExpTime) { 11 int expTime = baseExpTime + (int)(Math.random() * baseExpTime); // 随机过期时间 12 jedis.setex(key, expTime, value); 13 } 14 15 public String getCachedData(String key) { 16 return jedis.get(key); 17 } 18 19 public static void main(String[] args) { 20 Jedis jedis = new Jedis("localhost", 6379); 21 CacheAvalancheExample cache = new CacheAvalancheExample(jedis); 22 23 String key = "dataKey"; 24 String value = "dataValue"; 25 int baseExpTime = 3600; // 基础过期时间 1 小时 26 27 cache.setCachedData(key, value, baseExpTime); 28 29 String cachedValue = cache.getCachedData(key); 30 System.out.println("Cached Value: " + cachedValue); 31 32 jedis.close(); 33 } 34} 35
2. 加锁或队列
通过加锁或者队列的方式,控制缓存失效时对数据库的访问,避免大量请求同时打到数据库。
示例代码:
1import redis.clients.jedis.Jedis; 2import redis.clients.jedis.params.SetParams; 3 4public class CacheAvalancheWithLockExample { 5 private Jedis jedis; 6 7 public CacheAvalancheWithLockExample(Jedis jedis) { 8 this.jedis = jedis; 9 } 10 11 public String getCachedData(String key, DataProvider provider, int cacheTime) { 12 String value = jedis.get(key); 13 if (value != null) { 14 return value; 15 } 16 17 String lockKey = key + ":lock"; 18 String requestId = String.valueOf(Thread.currentThread().getId()); 19 20 // 尝试加锁 21 boolean locked = tryGetLock(lockKey, requestId, 30000); // 锁定30秒 22 if (locked) { 23 try { 24 value = provider.getData(); 25 if (value != null) { 26 jedis.setex(key, cacheTime, value); 27 } 28 } finally { 29 releaseLock(lockKey, requestId); 30 } 31 } else { 32 // 等待一段时间后重试 33 try { 34 Thread.sleep(100); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 return getCachedData(key, provider, cacheTime); 39 } 40 return value; 41 } 42 43 private boolean tryGetLock(String lockKey, String requestId, int expireTime) { 44 SetParams params = new SetParams().nx().px(expireTime); 45 String result = jedis.set(lockKey, requestId, params); 46 return "OK".equals(result); 47 } 48 49 private void releaseLock(String lockKey, String requestId) { 50 if (requestId.equals(jedis.get(lockKey))) { 51 jedis.del(lockKey); 52 } 53 } 54 55 public interface DataProvider { 56 String getData(); 57 } 58 59 public static void main(String[] args) { 60 Jedis jedis = new Jedis("localhost", 6379); 61 CacheAvalancheWithLockExample cache = new CacheAvalancheWithLockExample(jedis); 62 63 String key = "dataKey"; 64 int cacheTime = 3600; // 缓存 1 小时 65 66 String value = cache.getCachedData(key, () -> { 67 // 模拟数据库查询 68 return "dataValue"; 69 }, cacheTime); 70 71 System.out.println("Cached Value: " + value); 72 73 jedis.close(); 74 } 75} 76
3. 双写策略
更新数据库的同时也更新缓存,可以保证缓存的一致性,减小缓存失效的几率。
示例代码:
1import redis.clients.jedis.Jedis; 2 3public class CacheAvalancheWithDoubleWriteExample { 4 private Jedis jedis; 5 6 public CacheAvalancheWithDoubleWriteExample(Jedis jedis) { 7 this.jedis = jedis; 8 } 9 10 public void updateData(String key, String value, int cacheTime) { 11 // 更新数据库 12 updateDatabase(key, value); 13 14 // 更新缓存 15 jedis.setex(key, cacheTime, value); 16 } 17 18 private void updateDatabase(String key, String value) { 19 // 模拟数据库更新操作 20 System.out.println("Database updated: " + key + " = " + value); 21 } 22 23 public String getCachedData(String key) { 24 return jedis.get(key); 25 } 26 27 public static void main(String[] args) { 28 Jedis jedis = new Jedis("localhost", 6379); 29 CacheAvalancheWithDoubleWriteExample cache = new CacheAvalancheWithDoubleWriteExample(jedis); 30 31 String key = "dataKey"; 32 String value = "dataValue"; 33 int cacheTime = 3600; // 缓存 1 小时 34 35 cache.updateData(key, value, cacheTime); 36 37 String cachedValue = cache.getCachedData(key); 38 System.out.println("Cached Value: " + cachedValue); 39 40 jedis.close(); 41 } 42} 43
4. 数据预热
在系统启动时预先将一些热点数据加载到缓存中,可以有效防止缓存雪崩。
示例代码:
1import redis.clients.jedis.Jedis; 2 3public class CacheWarmUpExample { 4 private Jedis jedis; 5 6 public CacheWarmUpExample(Jedis jedis) { 7 this.jedis = jedis; 8 } 9 10 public void warmUpCache() { 11 // 预先加载热点数据到缓存中 12 String key = "hotKey"; 13 String value = "hotValue"; 14 int cacheTime = 3600; // 缓存 1 小时 15 jedis.setex(key, cacheTime, value); 16 17 System.out.println("Cache warmed up: " + key + " = " + value); 18 } 19 20 public String getCachedData(String key) { 21 return jedis.get(key); 22 } 23 24 public static void main(String[] args) { 25 Jedis jedis = new Jedis("localhost", 6379); 26 CacheWarmUpExample cache = new CacheWarmUpExample(jedis); 27 28 cache.warmUpCache(); 29 30 String key = "hotKey"; 31 String cachedValue = cache.getCachedData(key); 32 System.out.println("Cached Value: " + cachedValue); 33 34 jedis.close(); 35 } 36} 37
总结
通过以上示例代码,您可以分别采用随机过期时间、加锁或队列、双写策略和数据预热等方法来解决Redis的缓存雪崩问题。合理使用这些方法,可以有效地减少对数据库的瞬时压力,提高系统的稳健性和可用性。
《Redis(81)Redis的缓存雪崩是什么?》 是转载文章,点击查看原文。