Redis 缓存穿透、雪崩和击穿问题及解决方案

Redis 缓存穿透、雪崩和击穿问题及解决方案

在使用 Redis 作为缓存时,可能会遇到 缓存穿透缓存雪崩缓存击穿 这三种常见问题,它们都会影响系统的稳定性和性能。下面详细讲解它们的定义、成因以及相应的解决方案。


1. 缓存穿透(Cache Penetration)

1.1 定义

缓存穿透指的是查询的数据在缓存和数据库中都不存在,导致每次请求都会直接访问数据库,进而对数据库造成很大压力。

1.2 产生原因

  1. 恶意攻击:攻击者有意构造大量不存在的 key 来请求,绕过缓存直接访问数据库。
  2. 程序错误:业务代码逻辑有漏洞,导致频繁访问数据库中没有的数据。

1.3 解决方案

  1. 缓存空值(推荐)

    • 当数据库查询结果为空时,将该 key 对应的值设置为 NULL默认值 并存入缓存,设置一个短暂的过期时间(如 1 分钟)。
    • 例如:
      value = redis.get(key)
      if value is None:
          value = db.query(key)
          if value is None:
              redis.set(key, "NULL", ex=60)  # 设置 1 分钟过期
          else:
              redis.set(key, value, ex=3600)  # 设置 1 小时过期
      
  2. 布隆过滤器(Bloom Filter)

    • 使用布隆过滤器存储所有可能存在的 key,在查询缓存和数据库之前,先检查 key 是否可能存在。
    • 例如:
      from bloom_filter import BloomFilter
      bf = BloomFilter(capacity=100000, error_rate=0.01)
      bf.add("existing_key")  # 添加已有 key
      if key not in bf:
          return None  # 直接返回,避免访问 Redis 和数据库
      
  3. 参数校验与拦截

    • 在业务逻辑层对 key 进行基本的格式校验,避免恶意构造的请求直接进入数据库查询。

2. 缓存雪崩(Cache Avalanche)

2.1 定义

缓存雪崩指的是大量缓存数据同时失效,导致瞬间大量请求涌向数据库,造成数据库负载过高甚至宕机。

2.2 产生原因

  1. 大量数据设置了相同的过期时间,导致缓存同时过期。
  2. Redis 故障或宕机,导致所有缓存数据失效。

2.3 解决方案

  1. 缓存过期时间加随机值(推荐)

    • 例如:
      import random
      redis.set(key, value, ex=3600 + random.randint(1, 600))  # 过期时间 3600~4200 秒
      
  2. 双层缓存策略

    • 设立两级缓存:
      • 一级缓存(Redis)
      • 二级缓存(本地缓存,如 Guava Cache 或 EhCache)
    • 当 Redis 失效时,查询本地缓存,减少数据库压力。
  3. 热点数据提前预热

    • 在大规模活动(如秒杀、促销)前,提前加载热点数据到缓存,防止瞬时大规模请求击穿缓存。
  4. 降级与限流

    • 在缓存失效导致数据库压力激增时:
      • 限流:限制请求速率,如 令牌桶漏桶算法
      • 降级:返回默认值、缓存旧数据、提示用户稍后重试。

3. 缓存击穿(Cache Breakdown)

3.1 定义

缓存击穿指的是某个热点 key 失效后,大量请求同时访问数据库,造成数据库瞬时压力过大。

3.2 产生原因

  1. 某些热点数据访问量极高,一旦该 key 过期,瞬时大量请求直接访问数据库。
  2. 并发查询同一 key,导致大量请求同时击穿缓存。

3.3 解决方案

  1. 设置热点数据永不过期(推荐)

    • 例如:
      redis.set("hot_key", value, ex=None)  # 不设置过期时间
      
  2. 互斥锁(分布式锁)

    • 例如:
      import time
      import redis
      
      client = redis.StrictRedis()
      
      def get_value_with_lock(key):
          value = client.get(key)
          if value is None:  # 缓存失效
              lock = client.setnx(f"lock:{key}", 1)  # 尝试加锁
              if lock:
                  client.expire(f"lock:{key}", 10)  # 设置锁超时时间
                  value = db.query(key)  # 查询数据库
                  client.set(key, value, ex=3600)  # 写入缓存
                  client.delete(f"lock:{key}")  # 释放锁
              else:
                  time.sleep(0.1)  # 等待一段时间后再查缓存
                  return get_value_with_lock(key)
          return value
      
  3. 异步更新缓存

    • 采用异步线程消息队列来更新缓存,避免数据库查询被并发请求阻塞。

总结

问题定义解决方案
缓存穿透查询数据在缓存和数据库中都不存在,导致所有请求都打到数据库缓存空值、布隆过滤器、参数校验
缓存雪崩大量 key 同时过期或 Redis 故障,导致数据库瞬时压力过大过期时间加随机值、双层缓存、预热数据、限流降级
缓存击穿热点 key 过期后,大量并发请求直接访问数据库热点数据永不过期、互斥锁、异步更新缓存

如果你的系统涉及大规模并发访问,那么就要考虑这些缓存问题,并采用合适的策略来防止性能瓶颈! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值