深入理解Redis缓存穿透、击穿、雪崩及解决方案

本文详细探讨了Redis缓存中的常见问题,包括缓存穿透、击穿和雪崩,提供了布隆过滤器、互斥锁、热点数据预加载等解决方案,以及Redis如何通过多级缓存和主从复制增强数据存储的稳定性和性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简介

Redis 简介

Redis是一个基于内存的数据结构存储系统,是一个支持键值对、发布/订阅、存储新闻资讯的高性能key-value存储数据库。

缓存作用与优化

缓存技术在Web开发中是比较重要的组成部分,常用于增强Web应用的性能和容错性。缓存通过将计算过的数据或提前读出数据放置在高速缓存中,当请求相同数据时直接从缓存中响应。因此,缓存对于加速应用响应时间、节省处理器资源等方面有着非常显著的作用。

为了更好的利用缓存,需要对缓存问题进行分类和解决方案。

二、缓存问题的分类

缓存穿透

缓存穿透是指查询一个不存在的数据,由于缓存没有数据,请求被透传到数据库,此时如果恶意用户不断发起不存在数据的查询,缓存就无法发挥效果,请求最终压垮数据库。这种情况需要对不存在数据加以处理,如使用Nginx缓存;、使用异常机制处理。

缓存击穿

缓存击穿是指对于一个存在的key,由于并发量大,同时失效,导致多个线程都去查询数据库,造成缓存击穿。为了避免此类情况,可以令所有线程等待第一个查询后再进行操作;或者使用互斥锁等机制限制并发访问。

缓存雪崩

缓存雪崩是指缓存中大量的key在同一时刻失效,导致瞬间有大量的请求直接访问数据库,严重影响数据库的性能和应用的稳定性。为了解决这个问题,可以引入缓存预热、设置不同的过期时间等措施。

三、缓存穿透的解决方案

布隆过滤器

布隆过滤器可以快速判断一个元素是否存在于一个集合中,因此可以用来验证请求的参数或者ID在数据库中是否存在,从而有效防止恶意攻击导致的缓存穿透。

import redis
from bitarray import bitarray

class BloomFilter:
  def __init__(self, capacity, error_rate):
    self.capacity = capacity
    self.error_rate = error_rate
    self.redis_client = redis.Redis()
    self.hash_count = int(-1 * (capacity * math.log(error_rate) / (math.log(2) ** 2)))
    self.bit_array_length = int(math.ceil((capacity * math.log(error_rate)) / math.log(1.0 / (2 ** math.log(2)))))
    self.redis_client.setbit('bloom_filter', self.bit_array_length, 0)

  def exists(self, key):
    for i in range(self.hash_count):
      hashed_index = hash(key + str(i)) % self.bit_array_length
      if not self.redis_client.getbit('bloom_filter', hashed_index):
        return False
    return True

  def add(self, key):
    for i in range(self.hash_count):
      hashed_index = hash(key + str(i)) % self.bit_array_length
      self.redis_client.setbit('bloom_filter', hashed_index, 1)

缓存空对象

当查询结果为空时,我们也可以将其缓存到Redis中,并给它一个较短的生命周期。这样,下次如果同样的查询请求再次到达时,就可以直接从缓存中返回空结果,而不会穿透到数据库。

def get_user_info(user_id):
  user_key = f'user:{user_id}'
  user_info = redis.get(user_key)
  if not user_info:
    # 从数据库中查询用户信息,如果查不到标记为空并将结果缓存到Redis中
    user_info = db.query_user_info(user_id)
    if not user_info:
      redis.set(user_key, '', ex=60)
    else:
      redis.set(user_key, user_info, ex=3600)
  return user_info

接口层校验

在应用层或者接口层增加验证机制,对于非法请求进行拦截。可以根据请求的参数特征、请求频率等信息进行识别,从而避免类似SQL注入攻击等请求穿透缓存。

四、缓存击穿的解决方案

互斥锁

在需要大量更新缓存的场景下,我们通常需要使用互斥锁来避免缓存击穿。比如,可以使用Redis的SETNX命令设置标记,当发现缓存过期时,先去获取锁,然后再去加载数据并更新缓存,同时释放该锁。

def get_user_info(user_id):
  user_key = f'user:{user_id}'
  user_info = redis.get(user_key)
  if not user_info:
    lock_key = f'{user_id}_lock'
    # 使用SETNX命令尝试获取锁,如果获取成功
    if redis.setnx(lock_key, 1):
      # 设置锁的超时时间避免死锁
      redis.expire(lock_key, 60)
      user_info = db.query_user_info(user_id)
      redis.set(user_key, user_info, ex=3600)
      # 解锁,删除锁标记
      redis.delete(lock_key)
  return user_info

热点数据提前加载

在缓存过期前提前加载数据,避免并发请求穿透直接访问数据库导致缓存击穿。可以设置缓存过期时间略长于预加载时间,保证数据一定能够预先加载到缓存中。

def preload_hot_data():
  hot_data_key = 'hot_data'
  hot_data = db.query_hot_data()
  redis.set(hot_data_key, hot_data, ex=600)

def get_hot_data():
  hot_data_key = 'hot_data'
  hot_data = redis.get(hot_data_key)
  if not hot_data:
    # 预加载热点数据到缓存
    preload_hot_data()
    hot_data = redis.get(hot_data_key)
  return hot_data

五、缓存雪崩的解决方案

增加缓存容错能力

当大量缓存同时失效时,可以通过应用多级缓存、加入容错机制等手段防止缓存雪崩。具体实现可以根据业务场景进行选择和调整。

数据预热

尽可能在业务低峰期前将缓存数据全部加载到缓存系统中,从而避免业务高峰期缓存穿透、缓存击穿导致的缓存雪崩。可以使用定时任务或者异步加载方式,将慢查询或热点数据提前加载到缓存中。

def preload_cache():
  user_keys = db.query_all_user_keys()
  for user_key in user_keys:
    user_info = db.query_user_info(user_key)
    redis.set(user_key, user_info, ex=3600)

# 定时任务,每天凌晨1点执行一次数据预热
scheduler.add_job(preload_cache, 'cron', hour='1')

六、Redis针对以上问题的解决方案

Redis是一款高性能的内存数据库,为了解决以上问题,它提出了以下两种解决方案:

多级缓存策略

缓存中间件作为缓存系统的一种,主要用来提高网站并发量、降低网站响应时间、减轻源站数据库的压力等。多级缓存指的是使用两种及以上的缓存技术来加速响应速度。

"""
多级缓存策略示例:
1.使用 Redis 作为一级缓存,存储频繁访问的热数据;
2.使用 Memcached 作为二级缓存,存储冷数据或者业务数据;
3.使用本地缓存作为三级缓存,存储session,减少请求量。
"""
import redis
import memcache

class Cache:
    def __init__(self):
        self.redis = redis.Redis(host='localhost', port=6379)
        self.memcache = memcache.Client(['127.0.0.1:11211'])

    def get(self, key):
        # 尝试从 Redis 中获取数据
        data = self.redis.get(key)

        if not data:
            # Redis 中没有该数据,尝试从 Memcached 中获取
            data = self.memcache.get(key)

            if not data:
                # Memcached 中也没有,则从本地缓存中获取
                data = self.local_cache.get(key)

        return data

    def set(self, key, data):
        # 三个缓存都存储数据
        self.redis.set(key, data)
        self.memcache.set(key, data)
        self.local_cache.set(key, data)

主从复制与持久化

为了提高 Redis 的稳定性和可靠性,Redis 提供了主从复制和持久化机制。主从复制指的是数据备份,将 Redis 数据库的数据复制到一台或多台 Redis 实例上,以实现读写分离及容灾恢复;持久化指的是通过 RDB 和 AOF 两种方式把内存中的数据保存到磁盘中,保证数据在 Redis 重启后依旧存在。

"""
主从复制示例:
1.创建一个 Redis 实例,将其设置为主服务器;
2.创建两个 Redis 实例,将其分别设置为从服务器;
3.从主服务器同步数据到两个从服务器。
"""

import redis

# 创建 Redis 实例,作为主服务器
redis_master = redis.Redis(host='192.168.1.100', port=6379)

# 创建两个 Redis 实例,作为从服务器
redis_slave1 = redis.Redis(host='192.168.1.101', port=6379)
redis_slave2 = redis.Redis(host='192.168.1.102', port=6379)

# 将从服务器连接到主服务器上,实现主从复制
redis_slave1.slaveof(redis_master.host, redis_master.port)
redis_slave2.slaveof(redis_master.host, redis_master.port)

# 可以通过以下命令查看主从状态
# redis-cli info replication
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

格林希尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值