Redis 从零到生产级掌握
Redis 不能只学“会 set/get”。真正到商业项目里,你要能解释:Redis 为什么快,什么时候会慢;缓存为什么会穿透、击穿、雪崩;大 Key 和热 Key 为什么危险;RDB/AOF 会不会丢数据;哨兵和 Cluster 到底解决什么问题;分布式锁为什么还要 token、Lua、续期和幂等兜底;Redis 和 MySQL 为什么只能做到最终一致。
一句话建立主线:
Redis 是以内存数据结构为核心的高性能数据系统,最常用来做缓存、临时状态、计数、限流、排行榜和分布式锁。它解决的是高频低延迟访问问题,但不能替代关系型数据库保存核心事实数据。
学习目标
学完这一页,你要能做到:
- 根据业务场景选择 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog。
- 解释 Redis 命令从网络事件到数据结构操作的执行过程。
- 解释 Redis 为什么快,以及为什么大 Key、慢命令、长 Lua 会拖慢整个实例。
- 设计 Cache Aside 缓存流程,并处理穿透、击穿、雪崩。
- 解释 TTL、过期删除、内存淘汰、RDB、AOF 的原理和取舍。
- 解释主从、哨兵、Cluster、slot、MOVED、ASK 的区别。
- 解释大 Key、热 Key、缓存一致性、布隆过滤器、分布式锁、Redlock 的生产边界。
- 排查 timeout、慢命令、连接池耗尽、内存高、CPU 高、主从延迟、Cluster 热点。
如果你已经读完主线,但还不知道怎么把 Redis 原理落到项目和排查里,继续做:Redis 商业场景训练营。它把资产详情缓存、穿透/击穿/雪崩、大 Key、热 Key、缓存一致性、分布式锁、哨兵和 Cluster 串成可验证训练。
学习路线
flowchart TD
A["基础命令<br/>key、ttl、string"] --> B["数据结构<br/>hash、list、set、zset"]
B --> C["缓存模式<br/>Cache Aside、TTL、空值"]
C --> D["高并发问题<br/>穿透、击穿、雪崩"]
D --> E["内存治理<br/>过期删除、淘汰、大 Key"]
E --> F["可靠性<br/>RDB、AOF、主从"]
F --> G["高可用和扩容<br/>哨兵、Cluster"]
G --> H["工程能力<br/>锁、限流、一致性、排查"]这条路线不能乱。你还没理解数据结构,就无法判断大 Key;还没理解缓存模式,就无法解释一致性;还没理解单线程和慢命令,就无法排查 timeout;还没理解主从异步,就无法回答故障切换为什么可能丢数据。
第一步:Redis 放在系统什么位置
Redis 通常站在应用和数据库之间,承担“加速”和“削峰”的角色。
flowchart TD
A["用户请求"] --> B["业务服务"]
B --> C{"是否适合 Redis"}
C -- "热点读、临时状态、计数" --> D["Redis"]
C -- "核心事实、强一致写入" --> E["MySQL"]
D -- "命中" --> F["快速返回"]
D -- "未命中" --> E
E --> G["回填缓存或返回"]适合 Redis 的数据:
| 数据 | 原因 |
|---|---|
| 热点商品、资产详情 | 读多写少,可从数据库重建 |
| 字典、配置 | 访问频繁,变更少 |
| 短期验证码、登录态 | 有过期时间 |
| 计数器、限流窗口 | 原子自增,低延迟 |
| 排行榜 | ZSet 天然按分数排序 |
| 分布式锁 | 短时间互斥,降低重复执行 |
不适合只放 Redis 的数据:
| 数据 | 原因 |
|---|---|
| 订单、支付、账户余额 | 强一致和审计要求高 |
| 唯一事实源 | Redis 故障切换和持久化都有丢失窗口 |
| 大量历史明细 | 内存成本高,查询模型不适合 |
| 复杂关系查询 | Redis 不擅长 Join 和复杂 SQL |
第二步:数据结构怎么选
Redis 的强大不在“能存字符串”,而在不同结构匹配不同访问方式。
| 类型 | 适合 | Demo |
|---|---|---|
| String | 缓存、计数、锁 | set user:1 value ex 3600 |
| Hash | 对象字段、购物车 | hset cart:1 sku:1 2 |
| List | 简单队列、时间线 | lpush queue task |
| Set | 去重、标签、共同好友 | sadd tag:java user:1 |
| ZSet | 排行榜、延迟队列 | zadd rank 100 user:1 |
| Bitmap | 签到、布尔状态 | setbit sign:2026 userId 1 |
| HyperLogLog | UV 估算 | pfadd uv:day user:1 |
选择时问三个问题:
- 查询是按 key 直接查,还是按字段查?
- 是否需要排序、TopN、范围查询?
- 数据会不会无限增长,形成大 Key?
排行榜用 ZSet:
zadd asset:hot:rank 100 asset:A001 80 asset:A002
zincrby asset:hot:rank 10 asset:A002
zrevrange asset:hot:rank 0 9 withscores购物车用 Hash:
hset cart:user:1001 sku:10001 2 sku:10002 1
hincrby cart:user:1001 sku:10001 1
hgetall cart:user:1001如果购物车很大,不要无脑 hgetall,要考虑分页、拆分或限制数量。
第三步:Redis 为什么快
Redis 快不是一句“内存快”就够了。
flowchart TD
A["客户端请求"] --> B["IO 多路复用监听事件"]
B --> C["读取命令"]
C --> D["命令线程顺序执行"]
D --> E["操作内存数据结构"]
E --> F["写响应"]快的原因:
| 原因 | 解释 |
|---|---|
| 内存操作 | 避免大量磁盘随机 IO |
| IO 多路复用 | 一个线程可以处理大量连接事件 |
| 命令执行模型简单 | 核心命令顺序执行,减少锁竞争 |
| 数据结构优化 | Hash、跳表、整数集合、压缩列表等按场景优化 |
| 命令粒度小 | 很多命令是 O(1) 或 O(logN) |
但是单线程也有代价:
| 问题 | 后果 |
|---|---|
| 大 Key 读取 | 单次序列化、网络传输、内存操作都变重 |
KEYS * | 扫描全库,阻塞其他命令 |
| 长 Lua | 脚本执行期间其他命令排队 |
| 大集合删除 | 同步释放内存可能阻塞 |
| 慢磁盘持久化 | AOF rewrite、fork、fsync 可能带来抖动 |
所以 Redis 高并发的前提是:单条命令足够快,数据结构不失控。
第四步:缓存读取全过程
最常见模式是 Cache Aside。
flowchart TD
A["请求资产详情"] --> B["查 Redis"]
B --> C{"缓存命中"}
C -- "命中" --> D["返回缓存"]
C -- "未命中" --> E["查 MySQL"]
E --> F{"数据库有数据"}
F -- "有" --> G["写 Redis,设置 TTL"]
F -- "没有" --> H["缓存空值,短 TTL"]
G --> I["返回结果"]
H --> IJava 伪代码:
public AssetDTO getAsset(Long assetId) {
String key = "asset:detail:" + assetId;
String json = redis.get(key);
if (json != null) {
return parse(json);
}
Asset asset = assetMapper.selectById(assetId);
if (asset == null) {
redis.set(key, "NULL", Duration.ofMinutes(3));
return null;
}
redis.set(key, toJson(asset), randomTtl(30, 60, TimeUnit.MINUTES));
return toDto(asset);
}为什么要随机 TTL:
- 避免大量 Key 同一时间过期。
- 降低缓存雪崩概率。
- 给缓存一致性提供自动修复窗口。
为什么缓存空值:
- 防止不存在 ID 每次都打数据库。
- 空值 TTL 要短,避免无效 Key 占用太多内存。
- 新增数据时要删除对应空值缓存。
第五步:穿透、击穿、雪崩
三者经常混淆,要按“请求打到数据库的原因”区分。
| 问题 | 本质 | 例子 | 方案 |
|---|---|---|---|
| 穿透 | 查不存在数据 | 随机 assetId | 参数校验、空值缓存、布隆过滤器 |
| 击穿 | 热点 Key 失效 | 热门资产缓存过期 | 互斥锁、逻辑过期、预热 |
| 雪崩 | 大量 Key 同时失效或 Redis 故障 | 批量缓存同 TTL | 随机 TTL、多级缓存、限流降级 |
击穿的互斥锁流程:
flowchart TD
A["热点 Key 未命中"] --> B{"抢重建锁"}
B -- "抢到" --> C["查数据库"]
C --> D["写缓存"]
D --> E["释放锁"]
B -- "没抢到" --> F["短暂等待或返回旧值"]逻辑过期流程:
flowchart TD
A["读取缓存 value"] --> B{"逻辑时间过期"}
B -- "未过期" --> C["直接返回"]
B -- "已过期" --> D["先返回旧值"]
D --> E{"是否抢到重建锁"}
E -- "是" --> F["异步查库并刷新缓存"]
E -- "否" --> G["不重复重建"]逻辑过期适合读多写少、允许短暂旧值的场景,例如首页配置、热门资产详情。不适合支付状态、库存扣减、权限强一致判断。
第六步:布隆过滤器防穿透
布隆过滤器由位数组和多个哈希函数组成。
flowchart TD
A["assetId"] --> B["hash1/hash2/hash3"]
B --> C["位数组多个位置"]
C --> D{"查询时是否都为 1"}
D -- "有任意 0" --> E["一定不存在"]
D -- "全是 1" --> F["可能存在"]为什么适合防穿透:
- 占用内存很小。
- 能快速拦截大量不存在 ID。
- 不存在不会误判为一定存在,只会误判为可能存在。
缺点:
| 缺点 | 说明 |
|---|---|
| 有误判 | 可能把不存在判断为可能存在 |
| 删除困难 | 多个元素可能共享 bit,不能随便清零 |
| 需要重建 | 删除多或数据变化大时要定期重建 |
商业用法:
资产平台把有效 asset_id 加入布隆过滤器。请求先做参数校验,再查布隆过滤器;如果判断一定不存在,直接返回空;如果可能存在,再查 Redis 和 MySQL。新增资产成功后增量加入,删除较多时定期重建。第七步:过期删除和内存淘汰
TTL 过期不代表 Redis 立刻删除 Key。Redis 结合惰性删除和定期删除。
flowchart TD
A["Key 设置 TTL"] --> B{"访问 Key"}
B -- "访问时发现过期" --> C["惰性删除"]
A --> D["后台定期抽样检查"]
D --> E["删除部分过期 Key"]
E --> F{"内存仍超限"}
F -- "是" --> G["按 maxmemory-policy 淘汰"]常见淘汰策略:
| 策略 | 含义 |
|---|---|
| noeviction | 内存满后写命令报错 |
| allkeys-lru | 所有 Key 中淘汰最近最少使用 |
| volatile-lru | 只在有 TTL 的 Key 中按 LRU 淘汰 |
| allkeys-lfu | 所有 Key 中淘汰低频访问 |
| volatile-ttl | 优先淘汰剩余 TTL 短的 Key |
生产建议:
- 缓存 Key 尽量设置 TTL。
- 不要把 Redis 当无限内存数据库。
- 监控
used_memory、evicted_keys、命中率。 - 如果发生淘汰,要评估是否会导致数据库回源暴涨。
第八步:RDB、AOF 和数据丢失窗口
Redis 持久化不是强一致事务日志。
| 方式 | 原理 | 优点 | 风险 |
|---|---|---|---|
| RDB | 定期生成内存快照 | 文件紧凑,恢复快 | 两次快照之间可能丢数据 |
| AOF | 追加写命令日志 | 丢失窗口更小 | 文件大,重放慢,需要 rewrite |
| 混合持久化 | RDB 快照 + AOF 增量 | 恢复兼顾速度和完整性 | 仍要接受配置带来的丢失窗口 |
AOF appendfsync:
| 配置 | 含义 |
|---|---|
| always | 每条命令尽量刷盘,可靠但慢 |
| everysec | 每秒刷盘,常用,可能丢约 1 秒 |
| no | 交给操作系统,性能好但风险大 |
缓存数据可以接受短暂丢失,因为能从 MySQL 重建;核心账本、订单、支付流水不能只靠 Redis 保存。
第九步:主从、哨兵和 Cluster
三者解决的问题不同。
| 架构 | 解决什么 | 不解决什么 |
|---|---|---|
| 主从 | 副本和读扩展 | 自动故障转移 |
| 哨兵 | 主节点故障自动切换 | 单主容量和写入上限 |
| Cluster | 分片扩容和分片级高可用 | 单 Key 热点、跨 slot 多 key 操作 |
哨兵故障转移:
flowchart TD
A["Sentinel 监控 Master"] --> B{"Master 主观/客观下线"}
B -- "确认下线" --> C["选举 Sentinel Leader"]
C --> D["选择一个 Slave 晋升"]
D --> E["其他 Slave 复制新 Master"]
E --> F["客户端更新连接"]Cluster slot:
flowchart TD
A["Key"] --> B["CRC16 计算 slot"]
B --> C["0-16383 slot"]
C --> D["定位 Master 节点"]
D --> E["执行命令"]MOVED 和 ASK:
| 重定向 | 含义 |
|---|---|
| MOVED | slot 稳定迁到新节点,客户端应更新 slot 缓存 |
| ASK | slot 正在迁移,客户端临时去目标节点执行一次 |
Cluster 不能天然解决单个热 Key,因为一个 Key 只能落一个 slot。
第十步:大 Key 和热 Key
大 Key 是单个 value 或集合太大,热 Key 是访问太集中。
| 问题 | 本质 | 例子 | 风险 |
|---|---|---|---|
| 大 Key | 单次操作太重 | 一个 Hash 几十万 field | 阻塞、网络慢、删除慢、复制慢 |
| 热 Key | 单点流量太高 | 秒杀商品库存 | 单节点 CPU/网络打满 |
发现大 Key:
redis-cli --bigkeys
redis-cli memory usage your:key
redis-cli slowlog get 20治理:
| 问题 | 方案 |
|---|---|
| 大 String | 拆字段、压缩、限制大小 |
| 大 Hash | 按业务维度拆分,用 HSCAN 分批 |
| 大 List/ZSet | 限制长度、分页、归档 |
| 删除大 Key | 用 UNLINK,避免同步释放阻塞 |
| 热 Key | 本地缓存、多级缓存、拆 Key、限流、请求合并 |
本地缓存要看一致性要求。字典、配置、热门详情可以短 TTL 本地缓存;权限、库存、支付状态不要随便本地缓存。
第十一步:缓存和数据库一致性
常见写策略是:先更新数据库,事务提交后删除缓存。
flowchart TD
A["写请求"] --> B["更新 MySQL"]
B --> C{"事务提交成功"}
C -- "成功" --> D["删除 Redis 缓存"]
D --> E{"删除是否成功"}
E -- "成功" --> F["结束"]
E -- "失败" --> G["记录重试/发送消息/CDC补偿"]
C -- "失败" --> H["不动缓存"]为什么不是先删缓存再更新数据库:
- 先删缓存后,还没更新数据库。
- 并发读发现缓存不存在,查到旧数据库。
- 并发读把旧值重新写入缓存。
- 写请求再更新数据库。
- 缓存里长期保留旧值。
删除缓存失败怎么办:
| 方案 | 说明 |
|---|---|
| 重试表 | 本地记录失败任务,后台重试 |
| MQ 补偿 | 删除失败发送消息异步处理 |
| Binlog CDC | 订阅数据库变更,修正缓存 |
| TTL 兜底 | 即使删除失败,过期后能恢复 |
缓存通常只能做到最终一致。强一致读写要回数据库或业务服务。
第十二步:分布式锁生产写法
加锁:
set lock:asset:A001 9f5c9b2a-uuid nx px 30000释放锁必须用 Lua 判断 token:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end为什么不能直接 del lock:
sequenceDiagram
participant A as 线程A
participant B as 线程B
participant R as Redis
A->>R: set lock tokenA nx px 30000
A->>A: 业务执行超时,锁过期
B->>R: set lock tokenB nx px 30000
A->>R: del lock
R-->>B: B 的锁被误删锁不是最终正确性的唯一保障。核心业务还要有:
- 数据库唯一约束。
- 幂等号。
- 状态机。
- Fencing Token。
- 超时补偿和重试。
第十三步:完整 Demo
资产详情缓存:
set asset:detail:A001 '{"assetNo":"A001","status":"IDLE"}' ex 1800
get asset:detail:A001热点排行榜:
zadd asset:visit:rank 1 A001
zincrby asset:visit:rank 1 A001
zrevrange asset:visit:rank 0 9 withscores限流计数:
incr rate:api:asset:1001
expire rate:api:asset:1001 60分布式锁:
set lock:collect:task:1001 token-uuid nx ex 30生产中这些命令不要孤立使用,要配合 TTL、异常处理、Lua、幂等和监控。
线上排查总流程
flowchart TD
A["Redis 线上问题"] --> B{"表现是什么"}
B -- "timeout" --> C["查应用连接池、网络、slowlog"]
C --> D["大 Key、慢命令、DB 回源"]
B -- "CPU 高" --> E["查 hot key、复杂命令、Lua"]
B -- "内存高" --> F["查 bigkeys、TTL、淘汰策略"]
B -- "命中率低" --> G["查穿透、雪崩、TTL、Key 设计"]
B -- "主从延迟" --> H["查大 Key、网络、AOF、写入峰值"]
B -- "Cluster 单节点高" --> I["查热 Key 和热点 slot"]常用命令:
redis-cli info
redis-cli slowlog get 20
redis-cli info commandstats
redis-cli --bigkeys
redis-cli memory usage your:key
redis-cli client list排查清单:
| 问题 | 先看什么 |
|---|---|
| timeout | 连接池、slowlog、网络、DB 回源 |
| 慢命令 | 大 Key、全量命令、长 Lua |
| 内存高 | bigkeys、TTL、无界集合、淘汰 |
| CPU 高 | 热 Key、复杂集合运算、Lua |
| 主从延迟 | 大 Key 写入、网络、从库负载 |
| Cluster 热点 | slot 分布、单 Key 访问量 |
| DB 压力突然高 | 缓存穿透、击穿、雪崩、Redis 故障 |
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
生产用 KEYS * | 阻塞 Redis | 用 SCAN 分批限速 |
大集合 HGETALL | 单次返回太大 | 分页、拆 Key、HSCAN |
| 缓存无 TTL | 内存长期占用、旧值难恢复 | 设置合理 TTL |
| Redis 当唯一数据库 | 故障或切换可能丢数据 | 核心事实落 MySQL |
直接 DEL 解锁 | 可能误删别人锁 | token + Lua |
| Cluster 以为能解决所有热点 | 单 Key 仍落单 slot | 本地缓存、拆 Key、限流 |
删除大 Key 用 DEL | 同步释放阻塞 | 用 UNLINK |
| 删除缓存失败不补偿 | 长时间脏缓存 | 重试、MQ、CDC、TTL |
面试标准回答
Redis 怎么从零学到生产可用
Redis 要按数据结构、缓存模式、高并发问题、内存治理、持久化、高可用、分布式锁和生产排查这条线学习。先掌握 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog 适合什么场景,再理解 Redis 为什么快、为什么单线程下慢命令会阻塞。缓存使用 Cache Aside,要处理穿透、击穿、雪崩、TTL、空值缓存和布隆过滤器。生产还要理解 RDB/AOF、主从、哨兵、Cluster、大 Key、热 Key、缓存一致性、分布式锁和 timeout 排查。Redis 为什么快,什么时候会慢
Redis 快主要因为数据在内存中,使用 IO 多路复用处理连接,核心命令执行模型简单,内置数据结构针对场景优化,很多命令复杂度低。但 Redis 不是永远快,大 Key、全量命令、长 Lua、复杂集合运算、AOF rewrite、主从同步、网络传输、连接池耗尽都会让它变慢。排查时要看 slowlog、commandstats、bigkeys、memory usage、连接池和应用回源情况。Redis 和数据库如何保证一致性
常见 Cache Aside 策略是先更新数据库,事务提交后删除缓存。删除失败时要有重试、MQ、Binlog CDC 或 TTL 兜底。不要简单先删缓存再更新数据库,因为并发读可能在数据库更新前读到旧值并重新写入缓存。缓存和数据库通常只能做到最终一致,强一致场景要读数据库或通过业务状态机保证。关联知识点
| 知识点 | 说明 |
|---|---|
| Redis 总览 | 专栏入口和学习顺序 |
| 命令执行与数据结构原理 | 为什么快、命令如何执行 |
| 过期删除与内存淘汰原理 | TTL、淘汰策略、内存治理 |
| 缓存问题 | 穿透、击穿、雪崩 |
| 布隆过滤器原理 | 防穿透的位数组和多哈希 |
| 主从哨兵 Cluster 全过程 | 高可用和分片 |
| 大 Key 与热 Key 治理 | 高并发热点治理 |
| 高并发缓存治理 | timeout、连接池、慢命令排查 |
| 分布式锁 | Redis 锁正确写法 |
| Redis 面试 | 标准回答和追问 |
本章小结
Redis 从零到生产级掌握,重点不是背命令,而是把数据结构、缓存模式、高并发问题、内存治理、持久化、高可用、锁和排查串起来。你要知道 Redis 能解决什么,也要知道它不能解决什么;知道为什么快,也知道什么时候会慢;知道怎么用,也知道出了问题怎么定位。
