云计算百科
云计算领域专业知识百科平台

【Redis#17】Redis 缓存

在这里插入图片描述

📃个人主页:island1314

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

  • 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》

🔥 目录

    • 一、什么是缓存
      • 1. 基本概念
      • 2. 缓存的层级结构(从快到慢)
      • 3. 缓存分类
      • 4. 缓存的特性
        • 关于缓存一致性问题
      • 5. 缓存 vs 缓冲(Buffer)
      • 6. 缓存的应用场景
    • 二、使用 Redis 作为缓存
      • 1. 一些问题
      • 2. Redis 的优势
    • 三、缓存的更新策略
      • 缓存的替换策略
    • 四、常见的缓存问题
      • 1. 缓存穿透(Cache penetration)
      • 2. 缓存击穿(Cache BreakDown)
      • 3. 缓存雪崩(Cache avalanche)
      • 4. 缓存污染
        • 关于 缓存预热(Cache preheating)
      • 5. 缓存不一致

一、什么是缓存

1. 基本概念

缓存(cache)是计算机中的一个经典的概念,在很多场景中都会涉及到核心思路就是把一些常用的数据放到触手可及(访问速度更快)的地方,方便随时读取。

缓存是将频繁访问的数据临时存储在访问速度更快的介质中,以提升整体性能的一种机制。

🧠 核心思想:

  • 时间换空间 :把“快”的地方用来装“常用”的数据
  • 局部性原理 :程序倾向于重复访问某些数据或其附近的数据(时间/空间局部性)

举个例子:

  • 比如我需要去高铁站坐高铁,我们知道坐高铁是需要反复刷身份证(数据)的 (进入高铁站,检票,上车乘车过程中,出站…)
  • 正常来说我的身份证是放在 皮箱(硬盘)里的(皮箱的存储空间大,足够能装)。但是每次刷身份证都需要开一次皮箱找身份证,就非常不方便.
  • 因此我就可以把身份证先放到 衣服口袋(缓存),口袋虽然空间小,但是访问速度比皮箱快很多这样的话每次刷身份证我只需要从口袋里掏身份证就行了,就不必开皮箱了
  • 此时"口袋"就是"皮箱"的缓存,使用缓存能够大大提高访问效率.

2. 缓存的层级结构(从快到慢)

缓存是一个典型的 分层结构(Hierarchy) ,每一层都作为下一层的缓存:

CPU 寄存器(Registers)

L1 Cache

L2 Cache

L3 Cache

内存(RAM)

硬盘(SSD/HDD)

网络(远程服务器)

层级特点示例
CPU 寄存器 极快,极小 几 KB,用于执行指令时快速访问数据
L1/L2/L3 Cache 快速访问,成本高 MB 级别,用于加速 CPU 对内存的访问
内存(RAM) 中等速度,容量较大 GB 级别,作为硬盘的缓存
硬盘(SSD) 容量大,速度较慢 TB 级别,可作为网络服务的本地缓存
网络 最慢,但容量无限 云端数据,需通过网络拉取
  • 硬盘相对于网络是"触手可及的",就可以使用硬盘作为网络的缓存,内存相对于硬盘是"触手可及的",就可以使用内存作为硬盘的缓存
  • CPU 寄存器相对于内存是"触手可及的",就可以使用 CPU 寄存器作为内存的缓存

这里所说的"触手可及"是个相对的概念

对于计算机硬件来说,往往访问速度越快的设备,成本越高,存储空间越小。缓存是更快,但是空间上往往是不足的。因此大部分的时候缓存只放一些 热点数据 (访问频繁的数据) 就非常有用了

3. 缓存分类

类型描述应用场景
硬件缓存 CPU 缓存、内存缓存 提升处理器访问数据的速度
操作系统缓存 文件系统缓存、磁盘缓存 加快文件读写、减少磁盘 I/O
应用缓存 Redis、Memcached、浏览器缓存 提高 Web 响应速度、数据库查询优化
CDN 缓存 将静态资源缓存到离用户最近的边缘节点 加快网站加载、降低源站压力
浏览器缓存 缓存网页资源(HTML、JS、CSS) 提高页面加载速度
数据库缓存 查询缓存、行缓存、索引缓存 避免重复执行复杂查询

4. 缓存的特性

特性说明
高速访问 缓存的数据访问速度快于原始数据源
有限容量 缓存通常较小,只保留热点数据
自动更新/淘汰 当缓存满时,会根据策略(LRU、LFU、FIFO)淘汰旧数据
透明性 应用无需关心数据来自缓存还是原始位置
一致性问题 缓存和原始数据可能不一致,需要刷新机制
关于缓存一致性问题

“缓存只是副本”,所以当原始数据发生改变时,缓存就可能失效。比如:

  • 你在衣服口袋里放了身份证 → 上车前突然换了张新身份证 → 口袋里的旧证就无效了
  • 浏览器缓存了网页内容 → 服务器端内容更新了 → 浏览器显示的是旧内容

解决方案:

  • 设置过期时间(TTL)
  • 使用版本号控制(ETag)
  • 主动清理缓存(如 CDN 刷新)
  • 使用缓存穿透、缓存击穿、缓存雪崩的防护机制(Redis 场景常见)

5. 缓存 vs 缓冲(Buffer)

很多人容易混淆这两个概念,其实它们的区别如下:

概念缓存(Cache)缓冲(Buffer)
目标 提高访问速度 临时存储数据,平衡速率差异
方向 读操作为主 读写都可能
用途 保存已存在的数据副本 保存即将写入或读取的数据
例子 Redis 缓存数据库数据 TCP/IP 协议栈缓冲区、硬盘读写缓冲

6. 缓存的应用场景

场景缓存作用
Web 浏览器 缓存图片、脚本、样式表,加快页面加载
操作系统文件系统 缓存经常访问的文件,减少磁盘读写
数据库缓存 缓存查询结果,避免重复查询
Redis / Memcached 作为数据库的缓存层,缓解数据库压力
CDN 缓存 缓存静态资源,让用户就近访问
NAS 异地访问 使用“节点小宝”建立 P2P 缓存链路,实现高速访问
远程唤醒 + 缓存路径映射 在未开机状态下也能触发远程访问,访问本地设备上的缓存目录

二、使用 Redis 作为缓存

在一个网站中,我们经常会使用关系型数据库(比如 MySQL) 来存储数据关系型数据库虽然功能强大,但是有一个很大的缺陷,就是性能不高(换而言之,进行一次查询操作消耗的系统资源较多)

1. 一些问题

问题一:为什么说关系型数据库性能不高?

  • 数据存储在硬盘上: 硬盘(尤其是机械硬盘)的 I/O 性能远低于内存
  • 查询未命中索引:全表扫描会增加磁盘读取次数,效率低下
  • SQL 解析开销大:包括语法解析、执行计划生成、事务管理等
  • 复杂查询耗资源: JOIN、GROUP BY、ORDER BY 等操作消耗大量 CPU 和内存
  • 并发访问竞争激烈: 多个请求同时访问数据库,容易造成锁竞争、连接池耗尽
  • 因此,如果访问数据库的并发量比较高,对于数据库的压力是很大的,很容易就会使数据库服务器宕机

    📌 类比说明:你可以把数据库看作图书馆的“图书管理系统”

    • 每次查书要翻目录、去书架找书 → 耗时
    • 如果很多人同时查书、借书、还书 → 图书馆就拥堵了

    而 Redis 就像你随身带的笔记本,记录了热门书籍的位置:不需要每次都跑图书馆,并且 高速查找 + 快速返回

    问题二:为什么并发量高了就会宕机?

    这是由 硬件资源 限制决定的,如下:

    资源并发影响
    CPU 单核处理能力有限,高并发导致任务堆积
    内存 连接数太多,占用大量内存
    磁盘 IO 频繁读写导致磁盘瓶颈
    网络带宽 请求太多,网络堵塞
    连接池限制 数据库最大连接数被占满

    比喻解释:想象你在餐厅吃饭,厨师只有一个:

    • 一个人点菜 → 厨师可以应付
    • 一百个人同时点菜 → 厨师手忙脚乱,服务崩溃 😵‍💫

    所以我们要做两件事:

  • 让厨师少干活 → 引入缓存,减少数据库访问
  • 多请几个厨师 → 使用数据库集群、主从复制、分库分表等手段
  • 如何让数据库能够承担更大的并发量呢?核心思路主要是两个:

    方式描述优点缺点
    开源(引入更多机器) 使用数据库集群、读写分离、分库分表 提升整体容量 成本高、维护复杂
    节流(引入缓存) 使用 Redis 缓存热点数据 成本低、见效快、部署简单 只能缓解读压力,无法解决写瓶颈

    实际开发中通常是两者结合使用,形成“读缓存 + 写数据库 + 分布式架构 ”的组合拳

    2. Redis 的优势

    Redis 就是一个用来作为数据库缓存的常见方案,因为 Redis 是一个高性能的内存数据库,它能显著减少对 MySQL 等关系型数据库的直接访问压力,从而提升整个系统的响应速度和并发能力。

    优势描述
    🧠 数据存在内存中 读写速度极快,适合高频访问
    ⚡ 单线程模型 避免上下文切换和锁竞争,性能稳定
    📦 支持多种数据结构 String、Hash、List、Set、Sorted Set、Bitmaps、HyperLogLog、Stream 等
    🔄 原子操作支持好 如INCR,SETNX,HINCRBY
    🧩 支持持久化 RDB/AOF 保证数据不丢失
    🌐 支持分布式 Redis Cluster、哨兵模式支持大规模部署

    Redis 访问速度比 MySOL快很多,或者说处理同一个访问请求,Redis 消耗的系统资源比 MySOL 少很多。因此 Redis 能支持的并发量更大

    • Redis 数据在内存中,访问内存比硬盘快很多
    • Redis 只是支持简单的 key-value 存储,不涉及复杂查询的那么多限制规则

    图解如下:

    image-20250712144721789

    • 客户端访问业务服务器,发起查询请求
    • 业务服务器先查询 Redis,看想要的数据是否在 Redis 中存在
      • 如果已经在 Redis 中存在了,就直接返回,此时不必访问 MySQL 了
      • 如果在 Redis 中不存在,再查询 MySOL.

    注意:缓存是用来加快“读操作"的速度的,如果是"写操作",还是要老老实实写数据库,缓存并不能提高性能.

    但实际中我们也会配合以下策略来优化写操作:

    方法描述
    Cache Aside(旁路缓存) 写数据库后更新或删除缓存
    Write Through(直写缓存) 同步更新缓存和数据库(一致性高,性能差)
    Write Behind(异步写) 更新缓存后延迟写入数据库(性能好,风险高)

    三、缓存的更新策略

    接下来还有一个重要的问题,到底哪些数据才是"热点数据"呢?

    1)定期生成:每隔一定的周期(比如一天/一周/一个月),对于访问的数据频次进行统计.挑选出访问频次最高的前 N%的数据.

    以搜索引擎为例.

    • 用户在搜索引擎中会输入一个"查询词",有些词是属于高频的,大家都爱捜(鲜花,蛋糕,同城交友,不孕不育…).有些词就属于低频的,大家很少搜.
    • 搜索引擎的服务器会把哪个用户什么时间搜了啥词,都通过日志的方式记录的明明白白。然后每隔一段时间对这期间的搜索结果进行统计(日志的数量可能非常巨大,这个统计的过程可能需要使用 hadoop 或者 spark 等方式完成)。从而就可以得到"高频词表"

    这种做法 实时性 较低,对于一些突然情况应对的并不好。比如春节期间,“春晚"这样的词就会成为非常高频的词,而平时则很少会有人搜索"春晚”

    2)实时生成:先给缓存设定容量上限(可以通过 Redis 配置文件的 maxmemory 参数设定),接下来把用户每次查询:

  • 如果在 Redis 查到了,就直接返回
  • 如果 Redis 中不存在,就从数据库查,把查到的结果同时也写入 Redis。如果缓存已经满了(达到上限),就触发 缓存替换策略,把一些"相对不那么热门"的数据淘汰掉
  • 缓存的替换策略

    当缓存满了,就需要决定哪些数据被替换出去:

    策略说明
    FIFO(先进先出) 把缓存中存在时间最久的淘汰
    LRU(淘汰最久未使用) 记录每个 key 的最近访问时间,把最近访问时间最长的淘汰
    LFU(淘汰最少使用) 记录每个 key 的最近的访问次数,把访问次数最少的淘汰
    Random(随机淘汰) 从所有 key 中随机淘汰
    ARC / CAR 自适应缓存替换算法,更智能

    这里的淘汰策略,我们可以自己实现,当然 Redis 也提供了内置的淘汰策略,也可以供我们直接使用,Redis 内置的淘汰策略如下:

  • volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰
  • allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰.
  • volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key.
  • allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰.
  • volatile-random 当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据
  • allkeys-random :当内存不足以容纳新写入数据时,从所有key中随机淘汰数据
  • volatile-ttl:在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰(相当于 FIFO,只不过是局限于过期的 key)
  • noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错
  • 整体来说 Redis 提供的策略和我们上述介绍的通用策略是基本一致的,只不过 Redis 这里会针对"过期key"和"全部 key"做分别处理.

    四、常见的缓存问题

    问题描述解决方式
    缓存穿透 查询不存在的数据 使用空值缓存、布隆过滤器
    缓存击穿 热点数据过期瞬间大量请求压垮数据库 设置永不过期、互斥锁、逻辑过期时间
    缓存雪崩 大量缓存同时失效,请求全部打到数据库 设置不同过期时间、加缓存预热、限流降级
    缓存污染 缓存了冷门数据,浪费空间 使用 LFU、ARC 替换算法
    缓存不一致 Redis 和数据库数据不一致 使用 Cache Aside、监听 Binlog、定时同步

    1. 缓存穿透(Cache penetration)

    定义:查询一个既不在缓存也不在数据库的数据,导致所有请求都打到数据库上

    • 比如:访问的 key 在 Redis 和 数据库中都不存在,此时这样的 key不会被放到缓存上,后续如果仍然在访问该key,依然会访问到数据库。这就会导致数据库承担的请求太多,压力很大

    客户端

    业务服务器(如 Java / Python / PHP)

    [尝试从 Redis 获取数据]
    ↙ ↘
    命中 ✅ 未命中 ❌
    ↓ ↓
    返回 Redis 数据 [回源到 MySQL 查询]

    写入 Redis(可选)

    返回给客户端

    这就是典型的 缓存穿透机制 ,也是大多数网站缓存设计的标准做法

    原因:

    • 非法 key 查询(如攻击者构造不存在的 ID)
    • 数据删除后未清理缓存
    • 爬虫或恶意刷接口

    解决方案

    方法描述
    空值缓存(Null Caching) 将不存在的 key 缓存为null或特殊标记,并设置短 TTL(如 5 分钟)
    布隆过滤器(Bloom Filter) 快速判断某个 key 是否存在,避免无效访问数据库
    参数校验 在业务层增加 ID 合法性检查(如手机号格式、ID 是否合法)
    限流降级 使用令牌桶/漏桶算法限制非法请求频率

    2. 缓存击穿(Cache BreakDown)

    定义:指某个热点 key 过期瞬间,大量并发请求涌入数据库,造成压力峰值,甚至引起数据库宕机.(相当于缓存雪崩的特殊情况)

    类比:就像你衣服口袋里的身份证过期了,所有人都要开皮箱找 → 皮箱被堵死了!

    解决方案

    方法描述
    永不过期策略 设置逻辑过期时间,后台异步更新数据
    互斥锁(Mutex) 只允许一个线程回源更新缓存
    读写锁(Read/Write Lock) 控制多个线程同时读,但只允许一个线程写
    分布式锁(Redisson / Redlock) 多节点环境下统一控制缓存重建过程

    3. 缓存雪崩(Cache avalanche)

    定义:大量 key 在同一时刻失效,导致数据库瞬间承受巨大压力,甚至直接宕机。

    • 本来 Redis 是 MySOL的一个护盾,帮 MySOL 抵挡了很多外部的压力.一旦护盾突然失效了,MySQL自身承担的压力骤增,就可能直接崩溃.

    常见原因:

    • 所有 key 设置相同的过期时间
    • Redis 整体宕机恢复后缓存全部失效

    如何解决?

    方法描述
    设置随机过期时间 在基础 TTL 上加一个随机偏移(如 3600 + random(0~300) 秒)
    缓存预热 系统启动时主动加载热点数据
    高可用架构 使用 Redis Cluster、哨兵模式,保证缓存服务稳定

    4. 缓存污染

    🚨 指缓存中存储了大量低频冷门数据,浪费空间,降低命中率。

    🧠 举例:

    • 用户搜索“周杰伦”很频繁 → 应该缓存
    • 用户搜索“a”、“b”、“c” → 不应该缓存

    解决方案:

    方法描述
    使用 LFU 替换策略 淘汰最少使用的 key
    设置最大内存 + LRU 淘汰策略 自动淘汰最久未使用的 key
    区分冷热数据 热点数据单独缓存,冷门数据不缓存
    使用 ARC / CAR 算法 更智能地管理缓存生命周期
    关于 缓存预热(Cache preheating)

    背景:使用 Redis 作为 MySQL 的缓存的时候,当 Redis 刚刚启动,或者 Redis 大批 key失效之后,此时由于Redis 自身相当于是空着的,没啥缓存数据,那么 MySOL就可能直接被访问到,从而造成较大的压力

    • 因此就需要提前把热点数据准备好,直接写入到 Redis 中.使 Redis 可以尽快为 MySQL 撑起保护伞

    热点数据可以基于之前介绍的统计的方式生成即可,这份热点数据不一定非得那么"准确",只要能帮助MySQL抵挡大部分请求即可。随着程序运行的推移,缓存的热点数据会逐渐自动调整来适应当前情况

    5. 缓存不一致

    🚨 缓存和数据库之间的数据不同步,导致读取旧数据。

    🧠 常见场景:

    • 先写数据库,再删缓存(可能失败)
    • 先删缓存,再写数据库(可能中间插入脏数据)

    🔐 解决方案:

    方法描述
    Cache Aside(旁路缓存) 最常用方式,手动维护一致性
    Write Through(直写缓存) 同步更新缓存和数据库(适合强一致性)
    Write Behind(延迟写入) 异步写入数据库,性能好但风险高
    监听 Binlog 更新缓存 MySQL 的 binlog 监听 + Kafka + Redis 同步
    定时同步机制 定时任务扫描数据库更新缓存(适合非实时场景)

    【★,°:.☆( ̄▽ ̄)/$:.°★ 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【Redis】的内容,请持续关注我 !!

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Redis#17】Redis 缓存
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!