type
Post
status
Published
date
Nov 29, 2025
slug
redis1
summary
tags
技术探索
category
icon
password
你维护了一个商品详情服务,背后直连 MySQL。
平时没什么问题。
但一到大促,商品详情接口突然冲到 1 万 QPS,而 MySQL 稳定只能承受 5000 QPS。
剩下的 5000 QPS 怎么办?
硬扛?
硬扛的结果一般就是:MySQL 连接数打满、慢查询堆积、接口超时,最后商品服务和数据库一起被拖垮。
这时候我们要先想一个问题:
很多用户查的明明是同一个商品,为什么每次都要去 MySQL 查?
比如热门商品详情页,1 万个请求里,可能有 8000 个请求查的都是同一个商品。
既然数据变化没那么频繁,那能不能把热点数据放到一个更快的地方?
下一次再有人查,直接从这个地方返回,不要每次都打 MySQL。
这个“更快的地方”,就是缓存。
而 Redis,就是后端架构里最常用的缓存组件之一。
1. 最朴素的方案:本地缓存
我们知道,内存访问速度远快于磁盘。
MySQL 虽然也有 Buffer Pool,但它毕竟是一个完整的关系型数据库,不可能无限承接所有高频读请求。
所以最简单的优化方式就是:
在商品服务自己的内存里放一个 Map。
比如 Java 里可以这样理解:
key 是商品 id,value 是商品数据。
用户查商品时,流程变成这样:
这样一来,第一次查询会打到 MySQL,后面再查同一个商品,就可以直接从内存里返回。
这就是本地缓存。
本地缓存的优点非常明显:
快,非常快,因为数据就在应用进程自己的内存里。
原来 1 万 QPS 全部打到 MySQL。
现在可能只有 1000 QPS 真的落到 MySQL,其余请求都被本地缓存挡住了。
数据库压力一下就下来了。
但是,本地缓存有一个很大的问题。
2. 本地缓存的问题:多实例下不好管理
真实线上环境里,商品服务通常不会只部署一个实例。
你可能部署了 10 台商品服务。
如果每台机器都维护一份自己的本地缓存,就会出现几个问题。
第一个问题是:浪费内存。
同一个商品数据,在 10 个实例里各存一份。
实例越多,浪费越明显。
第二个问题是:数据不一致。
商品价格改了,A 实例的缓存更新了,B 实例可能还没更新,C 实例可能还是旧数据。
第三个问题是:缓存不好统一管理。
你想统一删除某个商品缓存,统一设置过期时间,统一观察命中率,本地缓存都不方便。
所以更自然的演进方向是:
能不能把每个服务实例里的 Map 抽出来,单独做成一个服务?
所有商品服务都访问同一个缓存服务。
这个缓存服务本质上就是:
一个放在远程机器上的大 Map。
这就是远程缓存。
Redis 的名字经常被解释为 Remote Dictionary Server,也就是远程字典服务。
这名字很形象。
它最朴素的理解就是:
一个独立部署的、多个服务共享的、高性能远程 Map。
当然,这只是第一层理解。
严格来说,Redis 不只是一个 Map,而是一个支持多种数据结构、过期淘汰、持久化、高可用和集群能力的内存数据存储系统。
3. 加入 Redis 后,查询链路变成什么样?
加入 Redis 后,商品查询链路就变成了:
这就是典型的 Cache Aside 模式,也叫旁路缓存模式。
它的核心逻辑是:
- 读请求先查 Redis;
- Redis 有数据,直接返回;
- Redis 没数据,再查 MySQL;
- 查完 MySQL 后,把结果写入 Redis;
- 下次再查同一个数据,就可以直接命中 Redis。
所以 Redis 在后端架构里的第一层价值是:
站在 MySQL 前面,帮 MySQL 挡住高频读流量。
很多人学 Redis,一上来就背:
Redis 是基于内存的 key-value 数据库。
这句话没错,但没有画面感。
更好理解的方式是:
Redis 是从业务系统的读性能问题里自然演化出来的远程缓存服务。
不过,这里也要注意一个面试高频点。
Cache Aside 很常用,但它不是银弹。
它提升了读性能,同时也引入了缓存一致性问题。
比如商品价格更新时,你要考虑:
是先更新 MySQL,还是先更新 Redis?如果数据库更新成功,缓存删除失败怎么办?如果并发读写导致旧数据又被写回缓存怎么办?
真实业务里,更新数据时通常不会直接更新缓存,而是更常见地采用:
先更新数据库,再删除缓存。
然后配合重试机制、延迟双删、消息队列、订阅 binlog 等方式,尽量缩短缓存和数据库之间的不一致窗口。
这部分不用在入门阶段展开太深,但你要知道:
读缓存很简单,缓存架构真正麻烦的地方,往往在写更新和一致性。
4. Redis 不只是缓存,而是远程数据结构服务器
如果 Redis 只支持:
那它确实只是一个远程 key-value 缓存。
但业务里不只有普通字符串缓存场景。
比如:
用户信息可能适合按字段存。
排行榜需要按分数排序。
标签系统需要去重。
消息流需要记录消费位置。
所以 Redis 不只是提供一个 Map,而是在服务端内存里提供了多种常用数据结构。
常见数据类型包括:
String:普通字符串缓存,比如商品详情、验证码、计数器
Hash:对象字段缓存,比如 user:1 下面的 name、age、city
List:列表结构,可以做简单队列
Set:无序去重集合,比如用户标签、抽奖去重
ZSet:带分数的有序集合,比如排行榜
Stream:消息流,比如轻量级消息队列、消费记录
比如:
这就是 Redis 和普通缓存框架不一样的地方。
普通缓存很多时候只是:
key → value
而 Redis 更像是:
key → 各种高性能内存数据结构
所以 Redis 才能衍生出很多常见用法:
- 缓存;
- 排行榜;
- 计数器;
- 限流器;
- 分布式锁;
- 会话存储;
- 消息队列;
- 延迟队列;
- 用户标签;
- UV 统计。
Redis 的本质是:
一个高性能的远程内存数据结构服务器。
5. 既然 Redis 是远程服务,为什么还能这么快?(文章05会详细讲)
有些人可能会疑惑:
本地 Map 快我能理解,因为就在 JVM 内存里。Redis 明明是远程服务,要走网络,为什么还这么快?
很多人回答 Redis 为什么快,只会说:
因为 Redis 基于内存,而且是单线程。
这个回答不能说错,但太浅。
Redis 快,不是靠某一个点,而是一整套短链路设计。
第一,Redis 的数据主要在内存中操作。
它不像传统数据库那样,每次请求都强依赖磁盘随机 I/O。
第二,Redis 内部针对不同数据类型设计了高效的数据结构。
比如 String、Hash、List、Set、ZSet,不同类型底层会根据数据规模和场景选择合适的编码方式。
第三,Redis 使用 I/O 多路复用。
这点很关键。
单线程不代表一次只能服务一个客户端。
Redis 可以通过 I/O 多路复用,让一个线程同时监听大量客户端连接的读写事件。
哪个连接有数据来了,就处理哪个连接。
这样就不需要给每个连接都创建一个线程,也就减少了大量线程切换和调度成本。
第四,Redis 的核心命令执行主要由主线程串行完成。
多个客户端看起来是在并发访问 Redis,但真正操作内存数据结构时,命令会被主线程快速地一个个执行。
这样可以避免大量锁竞争。
不过,这里要说严谨一点:
Redis 不是所有地方都只有一个线程。
Redis 的核心命令执行主要是主线程完成,但后台持久化、异步释放内存、网络 I/O 等环节都可能涉及子进程或其他线程。
Redis 6 以后还支持多线程网络 I/O,用来提升网络读写和协议解析的效率。
但 Redis 的核心命令执行,依然主要由主线程串行完成。
第五,Redis 使用自己的 RESP 协议。
RESP 协议比 HTTP 更简单,解析成本更低,更适合 Redis 这种高频、短命令的请求模型。
所以 Redis 快的本质,不是简单一句“基于内存”。
更完整的理解应该是:
Redis 把网络、协议、事件模型、命令执行和数据结构都设计得很短、很轻,所以单次请求处理链路非常短。
6. 数据都放内存里,内存不够怎么办?
Redis 快,是因为它主要操作内存。
但内存有一个问题:
贵,而且有限。
如果所有数据都一直往 Redis 里塞,迟早会把内存打满。
所以 Redis 需要两套机制。
一套是过期机制。
一套是淘汰机制。
这两个概念很容易混,但它们解决的问题不一样。
6.1 过期机制:这个 key 到点就不该用了
调用方可以给 key 设置过期时间。
比如:
意思是:
product:1001 这个 key 3600 秒后过期。
也可以单独设置:
这样 Redis 就知道:
这个 key 到期后不应该再被使用。
但这里有一个面试高频点:
key 到期,不代表 Redis 一定会在那个时间点立刻把它物理删除。
Redis 删除过期 key,主要有两种方式。
第一种是惰性删除。
当你访问某个 key 时,Redis 发现它已经过期了,就会把它删除,然后返回不存在。
第二种是定期删除。
Redis 后台会周期性抽样检查一部分 key,把其中已经过期的 key 清掉。
所以过期时间表达的是:
这个 key 从什么时候开始不应该再被使用。
6.2 淘汰机制:内存满了,该删谁?
过期机制解决的是:
key 到期了,该不该失效?
淘汰机制解决的是:
Redis 内存快满了,该删谁腾空间?
Redis 可以配置最大内存,比如:maxmemory 4gb
当 Redis 使用内存达到上限时,就会根据淘汰策略处理数据。
常见策略包括:
noeviction:
内存满后不再接收会导致内存增长的写请求,直接报错。
allkeys-lru:
从所有 key 中淘汰最近最少使用的 key。
volatile-lru:
只从设置了过期时间的 key 中淘汰最近最少使用的 key。
allkeys-lfu:
从所有 key 中淘汰使用频率最低的 key。
volatile-lfu:
只从设置了过期时间的 key 中淘汰使用频率最低的 key。
allkeys-random:
从所有 key 中随机淘汰。
volatile-random:
只从设置了过期时间的 key 中随机淘汰。
volatile-ttl:
优先淘汰更快过期的 key。
所以 Redis 的内存管理可以总结成一句话:
过期机制负责让 key 按时间失效,淘汰机制负责在内存不够时主动腾空间。
7. Redis 数据都在内存里,宕机是不是全没了?
Redis 的数据主要在内存里。
那问题来了:
Redis 一重启,内存数据是不是全没了?
如果什么都不做,确实会丢。
所以 Redis 提供了持久化机制。
常见方式有两种:RDB 和 AOF。
7.1 RDB:给内存做快照
RDB 可以理解成“游戏存档”。
Redis 每隔一段时间,把当前内存中的数据生成一份快照,保存到磁盘文件里。
如果 Redis 重启,就可以加载这个快照,把数据恢复回来。
RDB 的优点是:
文件紧凑,恢复速度快,适合备份和全量恢复。
但缺点也明显:
如果两次快照之间 Redis 挂了,这期间写入的数据可能会丢。
比如 10:00 做了一次快照,10:05 Redis 挂了。
那 10:00 到 10:05 之间写入的数据,就可能恢复不回来。
7.2 AOF:记录每一次写命令
AOF 的思路不一样。
它是把每次写命令追加到文件里。
比如:
Redis 重启时,只要把 AOF 文件里的命令重新执行一遍,就能恢复数据。
AOF 的优点是:
数据丢失更少。
但缺点是:
文件可能越来越大,恢复时也需要重放命令。
所以 Redis 还支持 AOF 重写。
比如同一个 key 被连续修改:
最终其实只需要保留:
这就是 AOF 重写的意义:
压缩历史命令,减少文件体积,加快恢复速度。
7.3 缓存场景下,持久化一定要开很强吗?
Redis 不同使用场景,对持久化的要求不一样。
如果 Redis 只是做缓存,数据本来就能从 MySQL 重新加载,那么 Redis 数据丢了虽然会造成缓存雪崩风险,但数据本身不是永久丢失。
这种场景更关注的是:
Redis 重启后不要让所有请求瞬间打爆 MySQL。
所以可以通过预热、限流、降级、分批加载热点 key 等方式处理。
但如果 Redis 承担的是计数器、队列、分布式状态、库存扣减中间状态等角色,那数据丢失的影响就严重得多。
这时就要更重视:
AOF 策略、刷盘频率、主从复制、故障切换、数据一致性风险。
所以不要机械地说:
Redis 一定要开 RDB 和 AOF。
更成熟的说法是:
要根据 Redis 在系统里的角色,决定持久化强度和可接受的数据丢失范围。
8. Redis 既然走网络,为什么不用 HTTP?
Redis 是远程服务,客户端和 Redis 服务端之间肯定要通信。
那它为什么不直接用 HTTP?
原因很简单:
Redis 追求的是极致的短链路和低开销。
HTTP 是通用协议,能力很强,但也更复杂。
Redis 面对的请求通常很短,比如:
这种场景不需要 HTTP 那么复杂的语义。
所以 Redis 使用自己的通信协议:RESP。
RESP 的特点是:
简单、紧凑、容易解析,适合高频短命令场景。
你在客户端写:
客户端会把它编码成 RESP 格式发给 Redis。
Redis 服务端解析后,执行命令,再把结果按 RESP 格式返回。
所以 Redis 的高性能,不只是服务端内存快,也包括协议设计足够轻。
9. 到这里,Redis 到底是什么?
讲到这里,我们可以重新回答这个问题:
Redis 是什么?
如果用一句比较官方的话说:
Redis 是一个基于内存的高性能数据结构存储系统。
但如果用更容易理解的话说:
Redis 是一个独立部署的、高性能的远程内存数据结构服务器。
它的演化逻辑其实很清楚:
更具体地说:
MySQL 扛不住高频读,所以我们需要缓存。
本地缓存多实例难管理,所以我们需要远程缓存。
普通 key-value 不够用,所以 Redis 提供了多种服务端数据结构。
内存有限,所以 Redis 提供了过期机制和淘汰策略。
内存数据怕丢,所以 Redis 提供了 RDB 和 AOF。
单机 Redis 也会有故障和容量上限,所以后面继续演化出主从复制、哨兵和 Cluster。
它是从真实业务系统的性能问题里一步步演化出来的。
10. 总结
Redis 是一个高性能远程内存数据结构服务器,它经常被放在数据库前面,帮助数据库承接高频读流量,同时还能基于丰富的数据结构支持排行榜、计数器、限流、分布式锁、消息队列等场景。
到目前为止,我们讲的主要还是单机 Redis。
单机 Redis 性能很强,但还有几个问题没有解决:
Redis 挂了怎么办?数据怎么复制?单机内存不够怎么办?单机 QPS 到顶了怎么办?故障切换谁来做?
这些问题,就要靠 Redis 的主从复制、哨兵机制和 Cluster 集群模式来解决。
分享
