Redis 缓存

Redis 典型应用:缓存机制与常见问题

Redis 最常见、也最有代表性的应用场景之一就是做缓存。缓存并不是 Redis 独有的概念,而是计算机系统中非常经典的一种性能优化手段。它的核心思想很简单:把经常访问的数据放到离使用者更近、访问速度更快的位置,从而减少访问慢速存储或远程服务的次数。

9.1 什么是缓存

缓存的本质,是用更快的存储空间保存一部分高频访问的数据。

可以把缓存理解成"随手可取"的位置。比如坐高铁时,身份证原本可以放在行李箱里,但进站、检票、乘车、出站都可能要反复刷身份证。如果每次都打开行李箱去找,就很麻烦。更好的做法是把身份证临时放进口袋里,需要时直接拿出来。这里的"口袋"就是"行李箱"的缓存。

在计算机系统中也类似。不同硬件或访问路径的速度通常可以粗略理解为:

text 复制代码
CPU 寄存器 > 内存 > 硬盘 > 网络

因此,内存可以作为硬盘的缓存,硬盘也可以作为网络数据的缓存。访问越快的位置,通常成本越高、容量越小,所以缓存不可能保存所有数据。实际系统中,缓存通常只保存热点数据,也就是那些访问频率较高的数据。

这也对应了常说的"二八定律":少量热点数据往往能覆盖大部分访问请求。虽然具体比例不一定总是 20% 和 80%,但思想是一样的:只要把最常访问的一部分数据缓存起来,就能显著提升系统整体性能。

9.2 使用 Redis 作为缓存

在 Web 项目中,核心数据通常会存储在 MySQL 等关系型数据库中。关系型数据库功能强大,支持复杂查询、事务、约束和索引,但它也有明显的性能成本。

关系型数据库性能压力较大的原因主要包括:

  1. 数据主要存储在硬盘上,硬盘 IO 速度比内存慢,随机 IO 尤其明显。
  2. 如果查询没有命中索引,就可能发生全表扫描,进一步增加 IO 成本。
  3. SQL 执行前需要经过解析、校验、优化等步骤。
  4. 复杂查询,例如多表联合查询,会带来更高的计算和执行成本。

当访问并发量升高时,每个请求都会消耗 CPU、内存、磁盘 IO、网络带宽等资源。如果大量请求直接打到数据库,数据库很容易成为系统瓶颈,严重时甚至可能宕机。

提升数据库承载能力通常有两个方向:

  1. 开源:增加机器,部署更多数据库实例,例如主从复制、分库分表等。
  2. 节流:引入缓存,让热点数据优先从缓存读取,减少直接访问数据库的请求数量。

Redis 适合做缓存,主要是因为它的数据存储在内存中,访问速度远高于磁盘数据库;同时 Redis 采用 key-value 形式组织数据,操作模型相对简单,处理同样一次查询请求时通常消耗更少资源,能够支撑更高并发。

使用 Redis 作为缓存时,典型查询流程如下:

  1. 客户端向业务服务器发起查询请求。
  2. 业务服务器先查询 Redis。
  3. 如果 Redis 中存在目标数据,直接返回结果。
  4. 如果 Redis 中不存在目标数据,再查询 MySQL。
  5. 查询 MySQL 后,可以把结果写入 Redis,方便后续请求命中缓存。

这样一来,Redis 就像挡在 MySQL 前面的一层保护。大量热点请求会被 Redis 承接,只有缓存未命中的请求才会继续访问数据库。

需要注意的是,缓存主要优化的是读操作。对于写操作,最终仍然要以数据库为准,不能简单地认为加了缓存就能提升所有场景下的性能。

9.3 缓存的更新策略

既然缓存空间有限,就必须考虑一个问题:哪些数据应该进入缓存?

课件中介绍了两类常见思路:定期生成和实时生成。

1. 定期生成热点数据

定期生成的做法,是每隔一段时间统计访问频次,然后挑选访问量最高的一批数据放入缓存。

例如搜索引擎可以记录用户搜索的关键词,再通过日志统计出一段时间内的高频搜索词。对于这些高频词,系统可以提前把对应结果放入缓存。这样用户再次搜索时,就可以更快返回结果。

这种方案的优点是思路清晰,适合有明显访问统计周期的业务。缺点是实时性较弱,对突发热点不够敏感。比如某个节日、新闻或活动突然出现时,短时间内会产生新的热点,而定期统计可能无法立刻反映这种变化。

2. 实时生成热点数据

实时生成的做法更加动态。系统可以先给 Redis 设置容量上限,例如通过 maxmemory 控制最大内存。当用户查询数据时:

  1. 如果 Redis 命中,直接返回。
  2. 如果 Redis 未命中,就查询数据库。
  3. 查询数据库后,把结果写入 Redis。
  4. 如果 Redis 空间已满,就根据淘汰策略删除一部分旧数据。

持续运行一段时间后,Redis 中自然会留下更常被访问的数据。也就是说,热点数据不是提前算好的,而是在访问过程中逐渐沉淀出来的。

3. 常见缓存淘汰策略

当缓存空间不足时,就需要决定淘汰哪些数据。常见策略包括:

策略 含义 淘汰对象
FIFO First In First Out,先进先出 最早进入缓存的数据
LRU Least Recently Used,最近最少使用 最久没有被访问的数据
LFU Least Frequently Used,最不经常使用 一段时间内访问次数最少的数据
Random 随机淘汰 随机选择某些数据

Redis 本身也提供了多种内置淘汰策略,主要可以分为"只从设置了过期时间的 key 中淘汰"和"从所有 key 中淘汰"两类:

Redis 策略 说明
volatile-lru 从设置了过期时间的 key 中,使用 LRU 淘汰
allkeys-lru 从所有 key 中,使用 LRU 淘汰
volatile-lfu 从设置了过期时间的 key 中,使用 LFU 淘汰
allkeys-lfu 从所有 key 中,使用 LFU 淘汰
volatile-random 从设置了过期时间的 key 中随机淘汰
allkeys-random 从所有 key 中随机淘汰
volatile-ttl 从设置了过期时间的 key 中,优先淘汰更早过期的 key
noeviction 默认策略,内存不足时新写入直接报错

这些策略没有绝对好坏,关键要结合业务特点选择。如果缓存中所有数据都可以被替换,allkeys-lruallkeys-lfu 往往更符合缓存场景;如果只有部分 key 适合被淘汰,则可以选择 volatile-* 系列策略。

9.4 缓存预热、缓存穿透、缓存雪崩和缓存击穿

引入缓存之后,系统读性能通常会明显提升,但缓存层也会带来一些新的问题。课件中重点介绍了缓存预热、缓存穿透、缓存雪崩和缓存击穿。

1. 缓存预热

缓存预热指的是在系统刚启动、Redis 刚恢复,或者大量缓存失效之后,提前把热点数据加载到 Redis 中。

如果 Redis 刚启动时里面没有任何缓存数据,那么原本应该由 Redis 承接的请求会直接访问 MySQL。对于高并发系统来说,这可能会让数据库突然承受很大压力。

缓存预热的做法就是提前准备一批热点数据,并主动写入 Redis。热点数据可以来自历史访问统计,不一定要求完全准确,只要能挡住大部分高频请求,就能让系统平稳度过启动阶段。随着系统持续运行,缓存内容也会逐渐根据真实访问情况自动调整。

2. 缓存穿透

缓存穿透指的是:请求查询的 key 在 Redis 中不存在,在数据库中也不存在。

由于数据库也查不到结果,这类 key 往往不会被写入缓存。如果后续继续频繁访问同样的非法 key,每次都会绕过 Redis 访问数据库,导致数据库压力增大。

缓存穿透常见原因包括:

  1. 业务设计不合理,缺少必要的参数校验。
  2. 开发或运维误操作,导致数据库中的部分数据被误删。
  3. 恶意攻击者不断请求不存在的数据。

常见解决方案有三种:

  1. 对查询参数做严格校验,例如手机号、用户 ID、商品 ID 等必须符合合法格式。
  2. 对数据库中也不存在的 key,在 Redis 中缓存一个空值,避免同一个 key 反复打到数据库。
  3. 使用布隆过滤器,在访问 Redis 和数据库之前,先判断 key 是否可能存在。

布隆过滤器可以用较小空间判断一个元素是否可能存在。它不能百分百确认"存在",但可以非常高效地判断"肯定不存在",因此适合用来拦截大量非法 key。

3. 缓存雪崩

缓存雪崩指的是:短时间内大量缓存 key 同时失效,导致大量请求突然打到数据库,造成数据库压力骤增,甚至宕机。

Redis 原本承担了保护数据库的作用。如果这层保护突然大面积失效,MySQL 就会从"只处理少量未命中请求"变成"直接承接大量请求",风险非常高。

缓存雪崩常见原因包括:

  1. Redis 实例故障。
  2. 大量 key 设置了相同或接近的过期时间,导致同一时间集中失效。

常见解决方案包括:

  1. 部署高可用 Redis 架构,例如主从、哨兵或集群,并完善监控报警。
  2. 不给部分核心热点 key 设置过期时间,或者由后台任务主动更新。
  3. 给 key 设置过期时间时加入随机因子,避免大量 key 在同一时刻过期。

4. 缓存击穿

缓存击穿可以看作缓存雪崩的一种特殊情况。它不是大量 key 同时失效,而是某一个热点 key 突然失效。由于这个 key 的访问量非常高,一旦它从缓存中消失,大量请求会同时访问数据库,也可能把数据库打垮。

例如某个热门商品、热门文章、秒杀活动信息,都可能成为热点 key。如果这个 key 过期的一瞬间有大量并发请求进来,就会形成缓存击穿。

常见解决方案包括:

  1. 通过统计发现热点 key,并设置为不过期,或者采用后台异步刷新机制。
  2. 使用互斥锁或分布式锁,限制同一时间只有一个请求去数据库加载数据,其余请求等待或返回降级结果。
  3. 对热点接口做服务降级,例如返回旧数据、默认数据或提示稍后再试。

总结

Redis 作为缓存,本质上是用内存中的热点数据减少对数据库的直接访问,从而提升读性能、降低数据库压力。使用 Redis 缓存时,需要重点理解以下内容:

  1. 缓存适合保存热点数据,不适合无差别保存所有数据。
  2. Redis 做缓存时,一般先查缓存,未命中再查数据库。
  3. 缓存更新可以通过定期生成或实时生成完成。
  4. 缓存空间不足时,需要结合业务选择合适的淘汰策略。
  5. 缓存预热、穿透、雪崩和击穿都是实际开发中必须考虑的问题。

缓存的价值不仅在于"快",更在于它能保护后端数据库,让系统在高并发场景下更加稳定。真正用好 Redis 缓存,不只是会写 getset,还要理解数据如何进入缓存、如何被淘汰,以及缓存异常时系统应该如何兜底。

相关推荐
林夕071 小时前
Qt QML与C++混合编程实战指南
java·开发语言·数据库
hyunbar1 小时前
高级 SQL 实战教程(华为云 DWS / PostgreSQL 版)
linux·服务器·数据库
Nayxxu1 小时前
Claude Prompt Caching 详解:缓存写入、缓存读取与成本计算
缓存·prompt
顾凌陵1 小时前
SQL注入漏洞
数据库·sql·oracle
小碗羊肉1 小时前
【Redis | 第一篇】Redis常见命令
数据库·redis·缓存
Devin~Y1 小时前
大厂Java面试实战:Spring Boot微服务、Redis缓存、Kafka消息队列与Spring AI RAG
java·spring boot·redis·kafka·mybatis·spring mvc·hikaricp
手握风云-1 小时前
Redis:不只是缓存那么简单(十二)
redis·缓存
洛水水1 小时前
数据库连接池详解
数据库·c++·mysql
小江的记录本1 小时前
【Java基础】集合框架: ArrayList vs LinkedList 核心区别、扩容机制(附《思维导图》+《面试高频考点清单》)
java·数据库·python·mysql·spring·面试·maven