Redis及缓存雪崩、缓存穿透

本文深入探讨Redis的特点、持久化方案(RDB与AOF),并详细分析缓存雪崩、穿透、预热、更新及服务降级等问题及其解决方案。

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

本文大纲

一、简介

二、Redis持久化方案

三、Redis的缓存雪崩,缓存穿透,缓存预热,缓存更新,缓存降级等问题及解决方案

一 、简介

1.Redis的特点

  • Redis是一种非关系数据库(不会像MySQL一样多个表直接存在直接关联关系),也是一种内存型数据库

  • Redis是单线程的,并且是直接基于内存的,所以执行效率会非常高
    为什呢Redis是单线程的呢?(经典面试题)
    官方回答 : 因为Redis是基于内存的,所以CPU不会是Redis的瓶颈,但是内存会是,而且多线程的实现比较麻烦,直接使用单线程更省事一点(PS:这真的是官方说的,虽然说em....有一点...你懂得,但真实原因就是这么简单)
    有图为证 :


如果有兴趣的话可以去看一下传送门
因为Redis是直接操作内存的,而且是单线程的,避免了频繁的切换上下文,所以速度非常快

2.Redis的数据结构

  • String : 最普通和常用的一种结构,key/value
  • Hash : 类似于HashMap,底层也是哈希表
  • List : 类似于LinkedList,底层是双向链表
  • Set : 类似于HashSet,于HashSet的特性一样,不允许重复数据,不记录元素添加顺序
  • SortSet : 会自动的进行排序的set
  • Streams(流) : 18年刚刚发布的一个新的数据结构,不是太了解,相关资料比较少

二 、Redis持久化方案

1.RDB(默认)

  • RDB是redis默认的持久化方式
  • RDB是采用快照的方式来进行数据持久化的,当符合快照的条件时Redis会自动对内存中的数据进行快照,然后持久化到硬盘中
  • 触发条件
    • 符合自定义配置的快照规则
    • 执行save或者bgsave命令
      • eg : save 60 10000 :表示1分钟内至少100个键被更改则进行快照。
    • 执行flushall命令
    • 执行主从复制操作
    • 在redis.conf中设置快照规则
      • 打开redis.config文件,202行,这是RDB的默认配置,满足下面三个之一就会被触发

      • 在247行,配置快照生成地址

      • 在237行可以配置生成快照的名称,默认是dump.rdb

  • 每次redis启动时都会去读取dump.rdb快照文件,将数据加载到内存中
  • 原理 : Redis使用fork函数复制一份当前进程的副本(创建一个子进程)父进程继续接收处理客户端的请求,子进程负责将内存中的数据存储到硬盘中,当子进程写入完所有数据之后会用新的rdb文件替换旧的rdb文件
  • 细节 : 快照的时候并不会修改原有的rdb文件,而是用新生成的替换旧的,所以rdb文件是一定存在的,rdb文件是经过压缩的二进制文件,所以占用内存会很少,并且方便读取
  • 优点 : 因为是复制除了一个子进程来实现数据存储,所以父进程还是可以继续响应客户端的请求,所以客户端基基本不会受到影响,而且rdb是压缩后的二进制文件,进行数据恢复的速度会比较快,所以rbd非常适合用来做数据备份
  • 缺点 : 必须要满足rdb的条件才会执行数据备份,所以有可能因为Redis的突然宕机导致部分数据丢失,所以设置快照条件时必须足够严谨

2.AOF

  • 默认情况下,AOF是处于关闭状态的
  • 只要当前指令会修改Redis中的数据,那么Redis就会将这条命令存储到硬盘中,比较消耗资源,当然我们也可以通过固态硬盘等硬件来提升性能
  • 操作 :
    • 可以通过修改 redis.conf 配置文件593行中 appendonly 参数开启 AOF 方式

    • 默认的文件名是 appendonly.aof,可以通过597行的 appendfilename 参数修改

    • 可以通过623行的appendfsync来修改写入策略
      • everysec(默认) : 每秒写一次
      • always : 每次有redis命令执行时就写一次,比较安全,但是效率较低
      • no : 由系统决定什么是后写入,有可能导致数据丢失

    • 是否在重写文件时进行写入操作,645行
      • no(默认) : 会阻塞正在重写的进程,执行写入操作
      • yes : 不会阻塞正在重写的进程,等到重写完成后再进行写入操作,性能较高

    • 设置重写的文件增长比例以及最小内存,664行
      • percentage : 表示当前 aof 文件大小超过上一次 aof 文件大小的百分之多少的时候会进行重写。
      • min-size : 重写时最小的aof内存大小

    • 异常处理机制,689
      • yes(默认) : 如遇到停电等异常,在恢复之后会继续重写
      • no : 恢复之后直接失败

  • 原理 :
    • Redis可以在AOF文件太大时都AOF中的命令进行重写,重写后的AOF文件中是恢复全部数据的最小命令合集(比如有一个key修改俩次的命令,那么只会保留最后一次的修改命令)
    • 重写操作是安全的,在重写过程中并不会删除掉旧的AOF文件,并且如果有新的命令还是会继续加入到旧的AOF文件中,当新的AOF文件完成后会对旧的AOF文件进行替换,并且将重写过程中新加入到旧的AOF中
      ​ 的命令追加到新的AOF文件中
    • AOF文件中保存的命令都是有序的,而且都是以Redis协议的格式进行保存的,所以容易读懂
  • AOF的修复
    • 如果Redis此时正在对AOF文件进行写入操作,此时如果Redis突然宕机的话,AOF就会受损,Redis不会加载破损的AOF文件
    • 修复步骤 :
      • 将当前的AOF进行备份
      • 使用Redis自带的redis-check-aof 程序,对原来的 AOF 文件进行修复.redis-check-aof --fix
      • 重启Redis服务器,让其重新加载AOF文件

3.RDB及AOF的选择

  • 如果对数据的安全性要求非常高的话,那么最好俩个一起开启,如果能允许分钟内的数据丢失的话,就选择RDB
  • 如果数据需要时常备份的话,最好开启RDB
  • 俩中可以同时开启,也可以只使用一个,但是如果都开启的话,Redis重启时只会加载AOF文件来恢复数据

三 、Redis会遇到的问题以及解决方案

1.缓存雪崩

  • 发生场景 : 当Redis服务器重启或者大量缓存在同一时期失效时,此时大量的流量会全部冲击到数据库上面,数据库有可能会因为承受不住而宕机
  • 解决方案 :
    • 均匀分布 : 我们应该在设置失效时间时应该尽量均匀的分布,比如失效时间是当前时间加上一个时间段的随机值
    • 熔断机制 : 类似于SpringCloud的熔断器,我们可以设定阈值或监控服务,如果达到熔断阈值(QPS,服务无法响应,服务超时)时,则直接返回,不再调用目标服务,并且还需要一个检测机制,如果目标服务已经可以正常使用,则重置阈值,恢复使用
    • 隔离机制 : 类似于Docker一样,当一个服务器上某一个tomcat出了问题后不会影响到其它的tomcat,这里我们可以使用线程池来达到隔离的目的,当线程池执行拒绝策略后则直接返回,不再向线程池中增加任务
    • 限流机制 : 其实限流就是熔断机制的一个版本,设置阈值(QPS),达到阈值之后直接返回
    • 双缓存机制 : 将数据存储到缓存中时存储俩份,一份的有效期是正常的,一份的有效期长一点.不建议用这个方案,因为比较消耗内存资源,毕竟Redis是直接存储到内存中的

2.缓冲穿透

  • 发生场景 : 此时要查询的数据不存在,缓存无法命中所以需要查询完数据库,但是数据是不存在的,此时数据库肯定会返回空,也就无法将该数据写入到缓存中,那么每次对该数据的查询都会去查询一次数据库
  • 解决方案 :
    • 布隆过滤 : 我们可以预先将数据库里面所有的key全部存到一个大的map里面,然后在过滤器中过滤掉那些不存在的key.但是需要考虑数据库的key是会更新的,此时需要考虑数据库 --> map的更新频率问题
    • 缓存空值 : 哪怕这条数据不存在但是我们任然将其存储到缓存中去,设置一个较短的过期时间即可,并且可以做日志记录,寻找问题原因

3.缓存预热

  • 其实这个不是一个问题,是一种机制,在上线前先将需要缓存的数据放到缓存中去,这个的实现很简单,可以在启动的时候放(数据比较小),做一个开关(一个隐秘的接口),定时刷新缓存

4.缓存更新

  • 这也不是一个问题,是一种机制,怎么样保证缓存中的key是实时有效的,以及及时的更新数据资源
    • 监测机制 : 定时去监测Redis,查看过期的缓存,
      问题 :
      在看到这里的时候我有一个问题,如果key过期了那么我要不要再将key重新放入缓存呢,如果放入的话我设置这个有效期就完全没有必要了,完全可以设置为永久有效
      我想了一个解决方案,我们可以对命中率做一个记录,如果这个key在最近一段时间内被频繁命中的话,我们就在失效时进行更新,否则就直接清除掉
    • 被动更新 : 每次请求过来时我们判断一下当前key是否失效,失效就重新查询存放到缓存中,这个问题不会涉及到监测机制那个问题

5.服务降级

  • 服务降级是不得已而为之的,在关键的时候丢卒保帅,保证核心功能正常运行
    • 服务拒绝 : 直接拒绝掉非核心功能的所有请求,其实基本就是直接废弃掉某些模块
    • 服务延迟 : 将请求加入到线程池中或队列中,延迟执行这些请求
  • 注意 : 服务降级一定要有对应的恢复策略,不能降下去就不回来了,我们可以监测服务的状态,当状态适当时恢复服务的正常使用
<think>首先,用户的问题是关于Redis缓存中的穿透雪崩问题的解决方案。我需要基于提供的引用内容来回答。 关键引用: - 引用[1]:提到缓存击穿的解决方案,包括防止接口并发行为、让缓存永不过期、分级缓存架构(本地缓存+Redis集群)、热点发现、熔断降级、异步更新。 - 引用[2]:解释缓存雪崩是大量数据同时失效导致数据库压力过大,解决方法包括设置不同过期时间。 - 引用[3]:讨论缓存击穿,如果不设置过期时间就不会有问题,但可能导致内存占用;建议逻辑过期方案。 - 引用[4]:描述缓存击穿的处理,使用互斥锁防止多个线程同时查询数据库。 用户的问题是“Redis缓存穿透雪崩的解决方案”,所以我需要覆盖穿透雪崩,但引用中也提到了击穿。用户指定了穿透雪崩,所以我应该专注于这两个。 - 缓存穿透:查询不存在的数据,导致每次请求都打到数据库。 - 缓存雪崩:大量缓存数据同时失效,导致请求涌向数据库。 现在,结构我的回答: 1. **解释问题**:简要解释什么是缓存穿透雪崩。 2. **提供解决方案**:基于引用,给出具体的解决方案。 3. **使用中文**:所有内容用中文。 4. **遵守格式**: - 行内数学表达式用$...$,但这里可能不需要数学表达式,因为主题是Redis缓存。 - 独立公式用$$...$$,但同样,可能不适用。 - 确保LaTeX语法正确。 - 生成相关问题。 - 在引用段落末尾添加引用标识,如[^1]。 回答结构: - 清晰、逐步解决问题。 - 引用相关部分时添加标识。 最终回答包括: - 回答部分。 - 相关问题部分。 步骤: 1. **介绍缓存穿透**: - 定义:查询不存在的数据,导致数据库压力。 - 解决方案:基于引用[1]和[3],使用布隆过滤器、缓存空值等。 2. **介绍缓存雪崩**: - 定义:大量缓存同时失效。 - 解决方案:基于引用[2],设置随机过期时间、热点数据续期等。 3. **综合方案**:基于引用[1],建议分级缓存、熔断降级等。 生成相关问题: - 例如:1. 如何实现布隆过滤器来防止缓存穿透?2. 什么是缓存击穿及其解决方案?3. Redis缓存的其他常见问题有哪些? 引用添加: - 在相关句子末尾添加[^n]。 例如: - "为了防止缓存穿透,可以使用布隆过滤器来过滤无效查询[^1]." 确保回答真实可靠:基于提供的引用,不要添加外部知识。</think>### Redis缓存穿透雪崩的解决方案 Redis缓存是提升应用性能的关键组件,但处理不当可能引发问题。缓存穿透雪崩是常见问题,会导致数据库压力过大甚至服务崩溃。以下我将基于可靠引用,逐步解释问题并提供解决方案,确保内容清晰、实用。 #### 1. **缓存穿透的解决方案** **问题定义**:缓存穿透是指查询一个不存在的数据(如无效ID),导致请求直接绕过缓存打到数据库。如果频繁发生,数据库可能被压垮[^1]。 **解决方案**: - **布隆过滤器(Bloom Filter)**:在查询前,使用布隆过滤器过滤无效key。布隆过滤器是一种概率型数据结构,能高效判断key是否存在。如果过滤器返回不存在,则直接拒绝请求,避免数据库访问[^1]。 - **缓存空值**:对于查询结果为空的key,将其缓存为一个特殊值(如null),并设置较短过期时间(如5分钟)。这样,后续相同请求会命中缓存,避免穿透[^3]。 - **接口限流**:对高频无效查询的接口进行限流,例如使用Redis的计数器或第三方工具(如Sentinel),限制每秒请求数,防止恶意攻击[^1]。 例如,在代码中实现缓存空值: ```python import redis r = redis.Redis(host='localhost', port=6379, db=0) def get_data(key): data = r.get(key) if data is None: # 查询数据库 db_data = query_database(key) if db_data is None: # 缓存空值,过期时间300秒 r.setex(key, 300, "NULL") return None else: r.setex(key, 3600, db_data) # 正常缓存 return db_data elif data == "NULL": return None # 处理空值缓存 else: return data ``` #### 2. **缓存雪崩的解决方案** **问题定义**:缓存雪崩是指大量缓存数据在同一时间失效(如设置相同过期时间),导致所有请求瞬间涌向数据库,造成数据库过载[^2]。 **解决方案**: - **随机过期时间**:为缓存key设置随机的过期时间(如基础时间+随机偏移量)。例如,正常缓存过期时间为3600秒,可添加随机数(±600秒),避免批量失效[^2]。 - **热点数据续期**:实时监控key的访问频率(如使用Redis的监控工具),对热点数据自动续期(延长过期时间),确保其永不过期或接近永不过期[^1]。 - **分级缓存架构**:采用本地缓存(如Caffeine)+ Redis集群的组合。本地缓存处理高频请求,Redis作为二级缓存;当Redis失效时,本地缓存缓冲请求,减少数据库压力[^1]。 - **熔断降级**:集成熔断工具(如Hystrix或Sentinel),在数据库压力过大时自动触发降级策略(如返回默认值或错误页面),保护数据库[^1]。 例如,设置随机过期时间: ```python import random import redis r = redis.Redis(host='localhost', port=6379, db=0) def set_cache(key, value, base_ttl=3600): random_offset = random.randint(-600, 600) # 随机偏移±10分钟 ttl = base_ttl + random_offset r.setex(key, ttl, value) ``` #### 3. **组合方案建议** 为了全面防护,推荐结合多个策略: - **异步更新缓存**:使用消息队列(如Kafka)异步重建缓存。当缓存失效时,不直接查询数据库,而是发送消息到队列,由后台任务处理,避免并发冲击[^1]。 - **监控与自动续期**:部署监控系统,实时识别热点数据和失效风险,自动调整过期时间[^1]。 - **数据库保护**:在应用层添加限流和熔断,确保数据库在高负载时不被击穿[^4]。 总结:缓存穿透的核心是过滤无效查询,而缓存雪崩的关键是分散失效时间。通过布隆过滤器、随机过期时间和分级缓存等方案,能有效降低风险。实际部署中,建议结合业务场景测试优化[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值