TECH SHARING · 后端技术系列
Redis
深度解析
从原理到生产实战,为什么它是后端工程师的 必修课
主题Redis 核心原理与实战
时长~45 min
规模20 页 · 6 大模块
01
初识 Redis
它是什么、凭什么流行、16 年发展脉络
P.03–04
02
高性能原理
10 万 QPS 背后的三大支柱与线程模型
P.05–07
03
数据结构
五大类型与各自的底层编码实现
P.08–09
04
持久化与内存
RDB / AOF / 过期删除 / 内存淘汰策略
P.10–13
05
高可用架构
主从复制 → 哨兵 → Cluster 演进路线
P.14–16
06
实战与避坑
缓存三大问题、经典场景、生产铁律
P.17–20
REmote Dictionary Server —— 一个开源的内存键值数据库,
由 C 语言编写,2009 年由 Salvatore Sanfilippo 创建,如今是缓存事实标准。
数据存在内存里,读写不碰磁盘,所以它是为速度而生的。
💾
可持久化
RDB 快照 + AOF 日志,重启不丢数据
🏗️
高可用
主从复制 · 哨兵自动故障转移 · Cluster 分片
🧩
丰富数据结构
不止 String,还有 Hash / List / Set / ZSet
// key-value store
user:1001→"zhang"
counter:pv→102400
session:abc→{...}
rank:today→[a,b,c...]
online→{u1,u2,u3}
一个 GET key 就是全部 ——
没有表、没有 Join、没有 SQL 解析,这就是快的起点。
01 / MILESTONES
16 年,六次关键跃迁
2009
v1.0
项目诞生
antirez 为解决实时统计负载,用 C 写出第一版
2013
v2.8
哨兵 Sentinel
自动故障转移,高可用开箱即用
2015
v3.0
官方集群
Redis Cluster 分布式方案发布
2017
v4.0
模块生态
Module 系统 + LFU + 混合持久化
2020
v6.0
多线程 IO
网络读写并行化 + ACL + 客户端缓存
2022
v7.0
Functions
服务端函数 + 分片 PubSub
2025
v8.0
回归开源
改用 AGPL 协议,内置向量检索
从个人项目到 最流行的 NoSQL 之一:每次大版本都在回答"生产环境还缺什么"
02 / PERFORMANCE
凭什么做到 10 万+ QPS
01
纯内存操作
数据全在内存,读写在纳秒~微秒级完成,彻底绕开磁盘 I/O 这个最大瓶颈
02
单线程 + epoll IO 多路复用
命令执行无锁、无上下文切换;单线程也能扛住几十万连接,因为瓶颈在网络不在 CPU
03
为速度而生的高级数据结构
SDS 字符串 O(1) 取长度、跳表实现 ZSet、listpack 紧凑存储,全是最优解
单机吞吐对比(简单读写)
// rough benchmark, single instance
02 / THREAD MODEL
单线程,为什么反而是优点
→
🌀
epoll 事件循环
IO 多路复用
一个线程监听全部 socket
→
⚙️
命令执行
单线程串行
无锁 · 无切换 · 天然原子
→
为什么不用多线程执行命令 经典面试题
- 瓶颈在内存和网络,不在 CPU —— 多线程收益小
- 无锁 → 没有死锁和竞争,实现极简,Bug 极少
- 单线程 → 所有命令天然原子,INCR 不需要加锁
- 避免了线程上下文切换的微秒级开销
6.0 之后的多线程 IO threads
- 只并行化 read()/write() 网络读写,命令执行仍是单线程
- 网络成为瓶颈时可提升约 1 倍 吞吐,默认关闭
- 另有后台线程做脏活:异步删除大 Key、关闭文件、AOF 刷盘
bio-close
aof-fsync
lazy-free
03 / DATA STRUCTURES
五大数据结构,各司其职
🔤
String
最基础类型,可存字符串、整数、二进制,支持原子自增
SET / GET / INCR
场景:缓存 · 计数器 · 分布式锁
🗂️
Hash
field-value 映射表,适合存对象,可单独改某个字段
HSET / HGET / HGETALL
场景:用户信息 · 购物车
📜
List
双向链表,两端 O(1) 进出,天然是队列/栈
LPUSH / RPOP / LRANGE
场景:消息队列 · 最新动态
🎯
Set
无序去重集合,支持交并差运算
SADD / SINTER / SCARD
场景:标签 · 共同关注 · 抽奖
🏆
ZSet
带 score 的有序集合,跳表实现,按分数范围查超快
ZADD / ZREVRANGE
场景:排行榜 · 延迟队列
03 / UNDER THE HOOD
同一类型,两副面孔
| 类型 | 元素少 / 小 | 元素多 / 大 | 切换阈值(默认) |
| String | int / embstr | raw | 44 字节 |
| Hash | listpack | hashtable | 128 项 或 64 字节 |
| List | quicklist(listpack 节点组成的双向链表) | 按节点大小压缩 |
| Set | intset / listpack | hashtable | 128 项 |
| ZSet | listpack | skiplist + dict | 128 项 或 64 字节 |
小数据用紧凑结构:省内存、缓存友好;超过阈值自动升级为查询更快的结构 —— 两头的好处都占了。
SDS 简单动态字符串
头部直接记长度:O(1) 取长度(C 字符串要遍历);二进制安全;空间预分配 + 惰性释放,追加不抖。
skiplist 为什么不用红黑树
范围查询(ZRANGE)顺着底层链表走即可,红黑树要中序回溯;实现简单无旋转、内存可调(节点层数概率化)。
skiplist + dict 双结构
ZSet 大数据时同时挂跳表和字典:按成员查分 O(1),按排名/范围查 O(logN),用内存换双倍速度。
04 / PERSISTENCE · RDB
RDB:拍快照
// bgsave fork flow
主进程继续接收命令
─fork()─▶
子进程遍历全量数据写 RDB
写时复制 COW
·
fork 瞬间共享内存页,子进程读到的永远是fork 那一刻的数据,主进程后续写入复制到新页,互不干扰
SAVE # 主进程执行,阻塞一切
BGSAVE # 后台执行(生产唯一正确姿势)
save 900 1 # 900秒内至少1次修改则自动触发
✓ 快照的优点
- 恢复极快:二进制紧凑格式,直接载入内存
- 文件小:适合定时备份 / 灾备传输
- 对日常性能几乎零影响(子进程干活)
✗ 快照的代价
- 丢数据窗口大:两次快照之间的写入,宕机即丢
- fork 瞬间内存翻倍风险(大实例 + 写入密集时)
04 / PERSISTENCE · AOF
AOF:记日记
// append command to log
写命令SET k v
→
aof_buf协议格式追加
→
fsync 落盘按策略执行
everysec
后台线程每秒一次
默认推荐:最多丢 1 秒,性能几乎无损
no
交给操作系统,30 秒一次
最快,但丢多少听天由命
🔄 AOF 重写 bgrewriteaof
日志越记越长?fork 子进程按当前数据状态生成等价最小命令集,替换旧日志 —— 100 万次 INCR 重写成 1 条 SET。
🧬 混合持久化(4.0+,推荐)
重写时把 RDB 塞进 AOF 头部:恢复速度接近 RDB,丢失窗口只有 AOF 间隔 —— 两全其美。
05 / MEMORY · EXPIRE
过期 Key,怎么删
🛋️
惰性删除 lazy
平时不管。访问某个 key 时顺手检查:过期就删掉并返回 nil。CPU 零浪费,但没人访问的过期 key 会赖在内存里。
GET k → 过期? → 是: DEL + return nil
↓否
正常返回 value
🧹
定期删除 periodic
默认每 100ms(hz=10)从带 TTL 的 key 中随机抽 20 个检查,删掉过期的;若过期占比超过 25%,立刻再抽一轮 —— 自适应节奏。
每100ms: 抽样20个 → 删过期
过期率 > 25% ? 重复 : 结束本轮
🤝
两者配合使用:惰性删保证正确性,定期删兜底内存占用。但若内存仍然涨满 maxmemory —— 就轮到下一页的淘汰策略出手了。
05 / MEMORY · EVICTION
内存满了,赶谁走
⚠️默认策略 noeviction:内存打满 maxmemory 后,所有写命令直接报 OOM 错误 —— 生产缓存场景几乎必须改掉它。
allkeys-lru
全体 key,按 LRU 淘汰
volatile-lru
只在设了 TTL 的 key 里 LRU
allkeys-lfu缓存首选
全体 key,按访问频率淘汰(4.0+)
volatile-lfu
有 TTL 的 key 里按频率淘汰
CONFIG SET maxmemory 4gb CONFIG SET maxmemory-policy allkeys-lfu
LRU 看最近访问时间(偶发批量扫描会误伤老 key),LFU 看访问频率更抗"一时热点";volatile-* 系列适合 Redis 里混存"缓存 + 不能丢的业务数据"时圈定淘汰范围。
06 / REPLICATION
主从复制:高可用的地基
全量复制 首次连接 / offset 丢失
从库首次连接:主库 bg 生成 RDB 发给从库载入,期间新写命令存进复制缓冲区随后追赶。代价大 —— 生产要避免频繁全量。
增量复制 repl_backlog + offset
断线重连时带上自己的 offset:还在环形积压队列里 → 只补差量命令;被覆盖了 → 退化为全量。网络抖动场景的救命稻草。
⚠️ 复制是异步的:主库不等从库确认就回 ACK 客户端 —— 主库刚写完就宕机,最后几条可能没同步到从库。极端场景可用 min-replicas-to-write 强制"至少 N 个从库在线才允许写"。
06 / SENTINEL
哨兵:7×24 的运维管家
集群拓扑(3 哨兵 × 1 主 2 从)
Sentinel-1
Sentinel-2
Sentinel-3
│ 监控 │ 监控 │
复制 / 复制
// quorum=2:少数服从多数,防止单个哨兵误判
故障转移四步(全自动,分钟级完成)
STEP 1
主观下线
单个哨兵 ping 主库超时,标记 sdown
→
STEP 2
客观下线
≥quorum 个哨兵都认为挂了 → odown,实锤
→
STEP 3
选举新主
按优先级 → 复制偏移量 → runid 挑最优从库
→
STEP 4
切换流量
其余从库改跟新主,客户端自动拿到新地址
哨兵自己也选主(raft 风格选 lead sentinel 执行切换);客户端连的是哨兵集群而非具体实例 —— 拿到的永远是当前主库地址,主库换了应用无感。
06 / CLUSTER
Cluster:数据自己分家
M1: 0–5460
M2: 5461–10922
M3: 10923–16383
🎲
哈希槽定位 CRC16(key) mod 16384
每个 key 落进唯一槽位,槽位分配到节点 —— 数据在哪台机器,算一下就知道,不需要中心路由表。
↪️
智能客户端 + MOVED 重定向
SDK 直连任意节点;key 不归它管就返回 MOVED 指路,客户端缓存槽位映射表,后续直连目标节点。
📡
Gossip 协议,无中心
节点间 P2P 交换集群状态,去中心化存活检测 + 故障转移(半数以上主节点同意才判定下线,比哨兵更稳)。
🧱
水平扩容 = 迁移槽位
加节点不用全量重分:把一部分 slot 从老节点在线迁移过去即可,标准部署 3 主 3 从起步。
07 / PITFALLS
缓存三大问题,年年面试年年挂
🕳️
缓存穿透
查根本不存在的数据(如 id=-1 恶意扫描):缓存永远 miss,每次都打到 DB,形同虚设。
防御方案
缓存空值:miss 后也把空值存 60s
布隆过滤器:前置一层,"不存在"直接拦
🔥
缓存击穿
热点 key 恰好过期那一瞬间,万级并发同时回源重建,DB 被打出一个尖刺。
防御方案
互斥锁:只放一个请求重建,其余等待
逻辑过期:物理不过期,值里带过期时间异步刷
🏔️
缓存雪崩
大面积同时失效(批量同 TTL 到期 / Redis 整体宕机),流量洪峰直接砸穿 DB。
防御方案
TTL 加随机偏移:把失效时间打散开
多级缓存 + 熔断限流:DB 前再垫一层保险
07 / IN PRODUCTION
经典落地:缓存是怎么工作的
⚡ Cache-Aside 旁路缓存模式 最常用
请求读数据
→
查缓存命中?
→
⚡ 返回~0.1ms
miss ↓
查 DB~50ms
→
回写缓存+ TTL
读:先缓存后 DB,miss 才落库并回写 · 写:先更新 DB,再 DEL 缓存,下次读自动回源
🔒
分布式锁
SET lock v NX EX 10,抢到才能执行,解决多节点互斥;Redisson 看门狗自动续期
🏆
实时排行榜
ZSet 天生有序,ZINCRBY 加分,ZREVRANGE 0 9 直接取 Top10
🚦
接口限流
INCR + EXPIRE 固定窗口,或 Lua 脚本实现滑动窗口,防刷防雪崩
07 / BEST PRACTICES
上生产前的 四条铁律
📦
拒绝大 Key
单 value 控制在 10KB 内。大 Key 会阻塞单线程、拖垮整个实例;拆分或压缩后再存,删除用 UNLINK 异步删
🔥
警惕热 Key
单 key 打到机器网卡上限时:本地缓存兜底 + 多副本拆分读写,监控先行
⏰
过期时间必设
不加 TTL = 内存慢性泄漏。过期时间加随机偏移,防止缓存雪崩集中失效
🛡️
持久化要权衡
纯缓存场景 RDB 甚至全关;数据重要用 混合持久化(everysec),别无脑 always
—— 这四条覆盖了生产 Redis 事故的 80% ——
07 / CHEAT SHEET
一页带走:核心速览
⚡ 性能
- 纯内存 + epoll 单线程 + 优化的结构 = 10 万+ QPS
- 6.0 多线程只加速 网络 IO,命令执行仍然单线程
💾 持久化
- RDB 快照恢复快 / AOF 日志丢得少
- 4.0+ 混合持久化 = RDB 头 + AOF 尾,推荐
🏗️ 高可用
- 主从(异步复制)→ 哨兵(自动故障转移)
- Cluster:16384 槽分片 + gossip,水平扩容
🚨 避坑
- 穿透(不存在)· 击穿(热点过期)· 雪崩(同刻失效)
- 大 Key / 热 Key / 必设 TTL,淘汰用 allkeys-lfu
记住这张表,Redis 面试的主干问题基本都在射程内。
🎹 彩蛋:五个命令走天下
// 掌握这些,日常 90% 的缓存需求就能开工
SET k v EX 60GET kDEL kINCR kEXPIRE k 60
redis.io · AGPL (8.0) · since 2009 · keep it simple, keep it fast
← → 方向键 / 空格 / 点击 翻页