Redis 缓存

目录

什么是缓存

[使用 Redis 作为缓存](#使用 Redis 作为缓存)

缓存的更新策略

定时生成

实时生成

典型问题

[缓存预热(Cache preheating)](#缓存预热(Cache preheating))

[缓存穿透(cache penetration)](#缓存穿透(cache penetration))

[缓存雪崩(Cache avalanche)](#缓存雪崩(Cache avalanche))

[缓存击穿(Cache breakdown)](#缓存击穿(Cache breakdown))


什么是缓存

缓存(cache) 是计算机中的一个经典概念,在很多场景中都会涉及到,其核心思路是:将高频访问、计算成本高、变化不频繁 的数据,临时存储在访问速度更快 的介质中,以减少对慢速数据源的请求,从而提升系统响应速度、降低后端压力

而这里的访问速度更快 ,是一个相对的概念

对于硬件的访问速度来说,通常是:CPU 寄存器 > 内存 > 硬盘 > 网络

那么,相比于网络,硬盘的访问速度更快,就可以使用硬盘作为网络的缓存 ;相比于硬盘,内存的访问速度更快,就可以使用内存作为硬盘的缓存

可以看到,缓存是一种用空间换时间 的策略,但往往访问速度更快的设备,成本越高,存储空间越小,因此,在大部分时候,缓存只能放一些热点数据(也就是访问频繁的数据)

然而,对于大部分的情况,都满足 "二八定律 ":在任何一组事物中,最重要的只占其中约 20%,其余 80% 尽管是多数,却是次要的

也就是说 20% 的热点数据,就能够应对 80% 的访问场景

因此,我们只需要将这些少量的热点数据缓存起来,就可以应对大多数场景,从而明显的提升系统性能

使用 Redis 作为缓存

在很多场景中,我们会使用关系型数据库(如 MySQL )来存储数据

关系型数据库的功能强大,但由于它在强一致性、复杂查询、数据安全等方面上做了深度优化,代价是牺牲了部分极致吞吐与低延迟,因此性能不高

因此,若访问关系型数据库的并发量较高,就会导致数据库压力较大,也就容易使数据库服务器宕机

那么,如何让关系型数据库承担更大的并发量呢?

我们可以从以下两个方面考虑:

  1. 开源:引入更多的机器,部署更多的数据库实例,构成数据库集群

  2. 节流:引入缓存,缓存经常访问的热点数据,从而降低直接访问数据库的请求数量

而相比于关系型数据库,Redis 的访问速度更快:

  1. Redis 的数据存储在内存中,访问内存比访问硬盘快得多

  2. Redis 只支持简单的 key-value 存储,不涉及复杂查询的限制规则

因此 Redis 是作为关系型数据库缓存的常见方案:

当客户端发起查询请求时,业务服务器会先从 Redis 上查询数据:

若在 Redis 上查询成功,直接获取数据,就不用再访问 MySQL 了

若在 Redis 上查询失败,则继续在 MySQL 上查询数据

若需要写数据,则需要修改 MySQL 上的数据,Redis 不能提高性能

缓存的更新策略

在实际场景中,我们如何确定哪些数据是热点数据呢?也就是说,我们应该缓存哪些数据到Redis上呢?

对于需要存储的热点数据,我们可以进行定时生成实时生成

我们先来看定时生成

定时生成

定时生成 :每隔一定的周期 (如 一天、一周、一个月),对访问的数据频次进行统计,挑选出访问频次最高的前 N% 数据,并将其进行缓存

例如,我们可以使用定时任务,按照固定周期(如一天),定期统计查询日志,从而得到热点数据

但是这种方式的实时性较低 ,对于突发情况无法很好的处理,例如,某个时刻,某条数据的访问频次突然增大,但其未进行缓存,就会导致数据库压力增大

实时生成

先为缓存设置容量上限 (通过 Redis 配置文件的 maxmemory参数指定)

接下来,对于用户的查询:

  1. 先在 Redis 中进行查询,若查询成功,直接返回

  2. 若在 Redis 中查询失败,就从数据库中查,并将查询结果写入 Redis 中,以便下次可直接查询 Redis

若缓存达到上限,就会触发缓存淘汰策略,将一些 "相对不那么热门" 的数据淘汰掉

而常见的淘汰策略有:

  1. FIFO(First In First Out,先进先出) :将缓存中存放时间最久的数据淘汰掉

  2. LRU(Least Recently Used,淘汰最久未使用) :记录每个 key 的最近访问时间,将最近访问时间最早的 key 淘汰掉

  3. LFU(Least Frequently Used,淘汰访问次数最少) :记录每个 key 最近一段时间的访问次数,将访问次数最少的淘汰掉

  4. Random(随机淘汰 ):从所有 key 中随机选一个淘汰掉

Redis 也内置了淘汰策略,与常见的淘汰策略基本一致,但会针对带有过期时间的key全部key 分别处理:

策略 作用范围 淘汰算法 适用场景
noeviction(默认) 全部 Key 不淘汰,直接拒绝写入(返回 -OOM command not allowed) 纯持久化存储、不允许丢数据的场景
allkeys-lru 全部 Key 最近最少使用(Least Recently Used) 纯缓存场景,热点数据保留(最常用
volatile-lru 仅带 EXPIRE的 Key LRU 缓存与持久化数据混存,只淘汰有过期时间的 Key
allkeys-lfu 全部 Key 最不经常使用(Least Frequently Used) 访问频率分布稳定、长周期热点(如排行榜、内容库)
volatile-lfu 仅带 EXPIRE的 Key LFU 混存场景 + 频率特征明显
allkeys-random 全部 Key 随机淘汰 访问模式完全均匀,或 benchmark 测试
volatile-random 仅带 EXPIRE的 Key 随机淘汰 同 allkeys-random,但只动过期 Key
volatile-ttl 仅带 EXPIRE的 Key 剩余 TTL 最短者优先 时间敏感型数据(如验证码、临时令牌)

典型问题

缓存预热(Cache preheating)

当使用 Redis 作为 MySQL 缓存时,若 Redis 刚启动,或 Redis 中大批 key 失效,此时 Redis 相当于是空的,没有什么缓存数据,此时缓存命中率极低,导致 MySQL 的访问量增大,从而增大其压力

因此,需要提前将热点数据准备好,直接写入 Redis 中,这些热点数据不一定"准确",但能帮助 MySQL 抵挡大部分请求,随着运行时间的推移,缓存中的热点数据会自动调整,从而适应当前情况

缓存穿透(cache penetration)

缓存穿透 :请求的数据在缓存中不存在,在数据库中也不存在。由于缓存未命中,请求直接穿透到数据库,若并发量大,就会导致数据库承担请求增多,压力增大

为什么会出现缓存穿透呢?

  1. 参数校验缺失:前端传入空字符串、越界 ID、非法格式等数据时,业务层未拦截无效请求

  2. 数据已删除:商品下架/用户注销后,缓存未清理,此时历史请求仍携带旧 Key,数据库中已无对应记录

  3. 恶意攻击/爬虫:攻击者故意构造不存在的 Key,绕过缓存直打数据库

那么,该如何解决缓存穿透问题呢?

  1. 加强参数校验:对要查询的参数进行严格的合法性校验,拦截参数不符合要求的请求

  2. 缓存空对象(cache null):针对数据库上不存在的数据,也将其缓存到 Redis中,避免后续频繁访问

3.布隆过滤器(Bloom Filter)筛选 :布隆过滤器结合哈希 + 位图的思想,使用较少的空间,存储所有存在的 key,通过布隆过滤器先判断 key 是否存在,若不存在,则直接拦截

缓存雪崩(Cache avalanche)

缓存雪崩大量缓存数据 在同一时间过期失效,或缓存服务整体宕机,导致请求瞬间全部穿透到数据库,引发数据库压力骤增甚至崩溃,即缓存层的集体失守,使数据库在极短时间内承受远超其处理能力的请求洪峰

为什么会出现缓存雪崩呢?

  1. Redis 中key批量过期:大批 Key 设置相同过期时间,导致整点同时过期

  2. Redis 服务不可用:Redis 主从节点全挂,或集群中超过一半的主节点都挂了,导致缓存服务不可用

那么,该如何解决现缓存雪崩问题呢?

  1. 在设置 key 的过期时间时添加随机因子,让过期时间随机化

  2. 部署高可用的 Redis集群 ,并完善监控报警系统

缓存击穿(Cache breakdown)

缓存击穿 :某个热点 Key缓存过期失效的瞬间,大量并发请求同时涌入,全部穿透缓存直接打到数据库,导致数据库瞬间负载飙升

与"雪崩"(大面积过期/宕机)和"穿透"(查无此数据)不同,击穿聚焦于高并发场景下的单个/少数热点 Key 失效引发问题

那么,该如何解决现缓存击穿问题呢?

  1. 基于统计方式发现的热点key,并设置其永不过期

  2. 进行必要的服务降级,如访问数据库时使用分布式锁,限制同时请求数据库的并发数

相关推荐
Rubin智造社1 小时前
Claude Code开发者大会系列8:从脚本到智能体——独立开发者的“AI原生”工作流转型
数据库·人工智能·独立开发者·agentic工作流·ai原生开发·实操指南
一条泥憨鱼1 小时前
深入理解 MySQL 索引:原理、分类与优化实战
数据库·mysql
一条泥憨鱼1 小时前
详解MySQL事务(超详细版)
java·数据库·mysql·spring·maven·后端开发
j7~1 小时前
【MYSQL】 数据库的常见数据类型--详解
数据库·mysql·decimal·varchar·数据库的基本类型
栗子~~10 小时前
JAVA - 二层缓存设计(本地缓冲+redis缓冲+广播所有本地缓冲失效) demo
java·redis·缓存
隔窗听雨眠10 小时前
多活部署、CDN加速与边缘缓存全链路优化实战
缓存
星星也在雾里11 小时前
PgBouncer 解决 PostgreSQL 连接数超限 + 可视化监控
数据库·postgresql
未若君雅裁11 小时前
MyBatis 一级缓存、二级缓存与清理机制
java·缓存·mybatis
雨辰AI12 小时前
SpringBoot3 + 人大金仓读写分离 + 分库分表 + 集群高可用 全栈实战
java·数据库·mysql·政务