redis缓存穿透和 缓存雪崩

打印 上一主题 下一主题

主题 816|帖子 816|积分 2448

在使用Redis作为缓存系统时,缓存穿透(Cache Penetration) 和 缓存雪崩(Cache Avalanche) 是两种常见的问题。它们会影响缓存系统的性能和稳定性。以下是这两种问题的具体解释及其解决方法。
缓存穿透(Cache Penetration)

缓存穿透是指查询一个在缓存和数据库中都不存在的数据,导致请求直接穿透到数据库,增加了数据库的负载。
成因


  • 恶意查询:

    • 攻击者故意查询一些在缓存和数据库中都不存在的数据。

  • 误操作:

    • 用户或系统误操作查询了不存在的数据。
      示例
      假设有一个用户查询接口,用户ID 123456 不存在于缓存和数据库中。
      1.   public async Task<User> GetUserAsync(int userId)
      2.   {
      3.           // 尝试从缓存中获取用户
      4.           string userJson = await _redisCache.GetStringAsync($"user:{userId}");
      5.           if (userJson != null)
      6.           {
      7.                   return JsonSerializer.Deserialize<User>(userJson);
      8.           }
      9.           // 从数据库中获取用户
      10.           User user = await _userRepository.GetByIdAsync(userId);
      11.           if (user != null)
      12.           {
      13.                   // 将用户存入缓存
      14.                   await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user));
      15.           }
      16.           return user;
      17.   }
      复制代码

假如查询的用户ID 123456 不存在,每次查询都会直接穿透到数据库,增加数据库负载。
解决缓存穿透的方法


  • 缓存空值:

    • 将查询结果为空的数据也缓存起来,设置一个较短的过期时间。

  • 布隆过滤器(Bloom Filter):

    • 使用布隆过滤器来预先判断一个请求是否大概命中缓存,减少对数据库的无效查询。

  • 参数校验:

    • 在查询缓存之前,对查询参数进行校验,确保参数的有效性。

  • 限流和熔断:

    • 使用限流(Rate Limiting)和熔断(Circuit Breaking)机制来控制对数据库的请求。

示例:缓存空值
  1.                 public async Task<User> GetUserAsync(int userId)
  2.                 {
  3.                         // 尝试从缓存中获取用户
  4.                         string userJson = await _redisCache.GetStringAsync($"user:{userId}");
  5.                         if (userJson != null)
  6.                         {
  7.                                 if (userJson == "null")
  8.                                 {
  9.                                         return null;
  10.                                 }
  11.                                 return JsonSerializer.Deserialize<User>(userJson);
  12.                         }
  13.                         // 从数据库中获取用户
  14.                         User user = await _userRepository.GetByIdAsync(userId);
  15.                         if (user != null)
  16.                         {
  17.                                 // 将用户存入缓存
  18.                                 await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(10));
  19.                         }
  20.                         else
  21.                         {
  22.                                 // 缓存空值
  23.                                 await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
  24.                         }
  25.                         return user;
  26.                 }
复制代码
示例:布隆过滤器
  1.                 public class BloomFilterExample
  2.                 {
  3.                         private readonly BloomFilter _bloomFilter;
  4.                         private readonly IRedisCache _redisCache;
  5.                         private readonly IUserRepository _userRepository;
  6.                         public BloomFilterExample(BloomFilter bloomFilter, IRedisCache redisCache, IUserRepository userRepository)
  7.                         {
  8.                                 _bloomFilter = bloomFilter;
  9.                                 _redisCache = redisCache;
  10.                                 _userRepository = userRepository;
  11.                         }
  12.                         public async Task<User> GetUserAsync(int userId)
  13.                         {
  14.                                 // 使用布隆过滤器预先判断用户ID是否存在
  15.                                 if (!_bloomFilter.Contains(userId))
  16.                                 {
  17.                                         return null;
  18.                                 }
  19.                                 // 尝试从缓存中获取用户
  20.                                 string userJson = await _redisCache.GetStringAsync($"user:{userId}");
  21.                                 if (userJson != null)
  22.                                 {
  23.                                         return JsonSerializer.Deserialize<User>(userJson);
  24.                                 }
  25.                                 // 从数据库中获取用户
  26.                                 User user = await _userRepository.GetByIdAsync(userId);
  27.                                 if (user != null)
  28.                                 {
  29.                                         // 将用户存入缓存
  30.                                         await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(10));
  31.                                 }
  32.                                 else
  33.                                 {
  34.                                         // 缓存空值
  35.                                         await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
  36.                                 }
  37.                                 return user;
  38.                         }
  39.                 }
复制代码
缓存雪崩(Cache Avalanche)

缓存雪崩是指在某个时间点,大量的缓存数据同时过期,导致大量请求直接穿透到数据库,增加数据库负载,甚至大概导致数据库崩溃。
成因


  • 缓存过期时间一致:

    • 大量缓存数据设置雷同的过期时间,导致同时过期。

  • 缓存预热失败:

    • 缓存预热(Cache Warm-up)机制失败,导致缓存数据在短时间内大量过期。

  • 系统重启或故障:

    • 系统重启或故障导致缓存数据被清除,大量请求直接穿透到数据库。

解决缓存雪崩的方法


  • 设置不同的过期时间:

    • 将缓存数据的过期时间设置为不同的随机值,避免同时过期。

  • 分批过期:

    • 将缓存数据分批设置不同的过期时间,减少短时间内大量数据过期的环境。

  • 缓存预热:

    • 系统启动时或定期预热缓存,确保缓存中有足够的数据,减少缓存数据被清除后的压力。

  • 限流和熔断:

    • 使用限流和熔断机制来控制对数据库的请求,防止数据库过载。

  • 本地缓存:

    • 使用本地缓存(如内存缓存)来暂时存储数据,减少对数据库的直接访问。

  • 双缓存:

    • 使用两级缓存,如Redis和本地缓存,提高缓存的稳定性和可靠性。

示例:设置不同的过期时间
  1.                 public async Task<User> GetUserAsync(int userId)
  2.                 {
  3.                         // 尝试从缓存中获取用户
  4.                         string userJson = await _redisCache.GetStringAsync($"user:{userId}");
  5.                         if (userJson != null)
  6.                         {
  7.                                 return JsonSerializer.Deserialize<User>(userJson);
  8.                         }
  9.                         // 从数据库中获取用户
  10.                         User user = await _userRepository.GetByIdAsync(userId);
  11.                         if (user != null)
  12.                         {
  13.                                 // 设置随机的过期时间
  14.                                 Random random = new Random();
  15.                                 int randomMinutes = random.Next(5, 15); // 随机过期时间在5到15分钟之间
  16.                                 await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(randomMinutes));
  17.                         }
  18.                         else
  19.                         {
  20.                                 // 缓存空值
  21.                                 await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
  22.                         }
  23.                         return user;
  24.                 }
复制代码
示例:缓存预热
  1.                 public class CacheWarmupExample
  2.                 {
  3.                         private readonly IRedisCache _redisCache;
  4.                         private readonly IUserRepository _userRepository;
  5.                         public CacheWarmupExample(IRedisCache redisCache, IUserRepository userRepository)
  6.                         {
  7.                                 _redisCache = redisCache;
  8.                                 _userRepository = userRepository;
  9.                         }
  10.                         public async Task WarmupCacheAsync()
  11.                         {
  12.                                 // 获取所有用户ID
  13.                                 List<int> userIds = await _userRepository.GetAllUserIdsAsync();
  14.                                 foreach (int userId in userIds)
  15.                                 {
  16.                                         User user = await _userRepository.GetByIdAsync(userId);
  17.                                         if (user != null)
  18.                                         {
  19.                                                 // 设置随机的过期时间
  20.                                                 Random random = new Random();
  21.                                                 int randomMinutes = random.Next(5, 15); // 随机过期时间在5到15分钟之间
  22.                                                 await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(randomMinutes));
  23.                                         }
  24.                                         else
  25.                                         {
  26.                                                 // 缓存空值
  27.                                                 await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
  28.                                         }
  29.                                 }
  30.                         }
  31.                         public async Task<User> GetUserAsync(int userId)
  32.                         {
  33.                                 // 尝试从缓存中获取用户
  34.                                 string userJson = await _redisCache.GetStringAsync($"user:{userId}");
  35.                                 if (userJson != null)
  36.                                 {
  37.                                         if (userJson == "null")
  38.                                         {
  39.                                                 return null;
  40.                                         }
  41.                                         return JsonSerializer.Deserialize<User>(userJson);
  42.                                 }
  43.                                 // 从数据库中获取用户
  44.                                 User user = await _userRepository.GetByIdAsync(userId);
  45.                                 if (user != null)
  46.                                 {
  47.                                         // 设置随机的过期时间
  48.                                         Random random = new Random();
  49.                                         int randomMinutes = random.Next(5, 15); // 随机过期时间在5到15分钟之间
  50.                                         await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(randomMinutes));
  51.                                 }
  52.                                 else
  53.                                 {
  54.                                         // 缓存空值
  55.                                         await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
  56.                                 }
  57.                                 return user;
  58.                         }
  59.                 }
复制代码
总结


  • 缓存穿透(Cache Penetration):
    成因:查询在缓存和数据库中都不存在的数据。
    解决方法:

    • 缓存空值
    • 布隆过滤器
    • 参数校验
    • 限流和熔断

  • 缓存雪崩(Cache Avalanche):
    成因:大量缓存数据同时过期,导致大量请求直接穿透到数据库。
    解决方法:

    • 设置不同的过期时间
    • 分批过期
    • 缓存预热
    • 限流和熔断
    • 本地缓存
    • 双缓存

参考资源

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦见你的名字

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表