Skip to content

Redis 从零到生产级掌握

Redis 不能只学“会 set/get”。真正到商业项目里,你要能解释:Redis 为什么快,什么时候会慢;缓存为什么会穿透、击穿、雪崩;大 Key 和热 Key 为什么危险;RDB/AOF 会不会丢数据;哨兵和 Cluster 到底解决什么问题;分布式锁为什么还要 token、Lua、续期和幂等兜底;Redis 和 MySQL 为什么只能做到最终一致。

一句话建立主线:

Redis 是以内存数据结构为核心的高性能数据系统,最常用来做缓存、临时状态、计数、限流、排行榜和分布式锁。它解决的是高频低延迟访问问题,但不能替代关系型数据库保存核心事实数据。

学习目标

学完这一页,你要能做到:

  1. 根据业务场景选择 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog。
  2. 解释 Redis 命令从网络事件到数据结构操作的执行过程。
  3. 解释 Redis 为什么快,以及为什么大 Key、慢命令、长 Lua 会拖慢整个实例。
  4. 设计 Cache Aside 缓存流程,并处理穿透、击穿、雪崩。
  5. 解释 TTL、过期删除、内存淘汰、RDB、AOF 的原理和取舍。
  6. 解释主从、哨兵、Cluster、slot、MOVED、ASK 的区别。
  7. 解释大 Key、热 Key、缓存一致性、布隆过滤器、分布式锁、Redlock 的生产边界。
  8. 排查 timeout、慢命令、连接池耗尽、内存高、CPU 高、主从延迟、Cluster 热点。

如果你已经读完主线,但还不知道怎么把 Redis 原理落到项目和排查里,继续做:Redis 商业场景训练营。它把资产详情缓存、穿透/击穿/雪崩、大 Key、热 Key、缓存一致性、分布式锁、哨兵和 Cluster 串成可验证训练。

学习路线

mermaid
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 通常站在应用和数据库之间,承担“加速”和“削峰”的角色。

mermaid
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
HyperLogLogUV 估算pfadd uv:day user:1

选择时问三个问题:

  1. 查询是按 key 直接查,还是按字段查?
  2. 是否需要排序、TopN、范围查询?
  3. 数据会不会无限增长,形成大 Key?

排行榜用 ZSet:

bash
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:

bash
hset cart:user:1001 sku:10001 2 sku:10002 1
hincrby cart:user:1001 sku:10001 1
hgetall cart:user:1001

如果购物车很大,不要无脑 hgetall,要考虑分页、拆分或限制数量。

第三步:Redis 为什么快

Redis 快不是一句“内存快”就够了。

mermaid
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。

mermaid
flowchart TD
    A["请求资产详情"] --> B["查 Redis"]
    B --> C{"缓存命中"}
    C -- "命中" --> D["返回缓存"]
    C -- "未命中" --> E["查 MySQL"]
    E --> F{"数据库有数据"}
    F -- "有" --> G["写 Redis,设置 TTL"]
    F -- "没有" --> H["缓存空值,短 TTL"]
    G --> I["返回结果"]
    H --> I

Java 伪代码:

java
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:

  1. 避免大量 Key 同一时间过期。
  2. 降低缓存雪崩概率。
  3. 给缓存一致性提供自动修复窗口。

为什么缓存空值:

  1. 防止不存在 ID 每次都打数据库。
  2. 空值 TTL 要短,避免无效 Key 占用太多内存。
  3. 新增数据时要删除对应空值缓存。

第五步:穿透、击穿、雪崩

三者经常混淆,要按“请求打到数据库的原因”区分。

问题本质例子方案
穿透查不存在数据随机 assetId参数校验、空值缓存、布隆过滤器
击穿热点 Key 失效热门资产缓存过期互斥锁、逻辑过期、预热
雪崩大量 Key 同时失效或 Redis 故障批量缓存同 TTL随机 TTL、多级缓存、限流降级

击穿的互斥锁流程:

mermaid
flowchart TD
    A["热点 Key 未命中"] --> B{"抢重建锁"}
    B -- "抢到" --> C["查数据库"]
    C --> D["写缓存"]
    D --> E["释放锁"]
    B -- "没抢到" --> F["短暂等待或返回旧值"]

逻辑过期流程:

mermaid
flowchart TD
    A["读取缓存 value"] --> B{"逻辑时间过期"}
    B -- "未过期" --> C["直接返回"]
    B -- "已过期" --> D["先返回旧值"]
    D --> E{"是否抢到重建锁"}
    E -- "是" --> F["异步查库并刷新缓存"]
    E -- "否" --> G["不重复重建"]

逻辑过期适合读多写少、允许短暂旧值的场景,例如首页配置、热门资产详情。不适合支付状态、库存扣减、权限强一致判断。

第六步:布隆过滤器防穿透

布隆过滤器由位数组和多个哈希函数组成。

mermaid
flowchart TD
    A["assetId"] --> B["hash1/hash2/hash3"]
    B --> C["位数组多个位置"]
    C --> D{"查询时是否都为 1"}
    D -- "有任意 0" --> E["一定不存在"]
    D -- "全是 1" --> F["可能存在"]

为什么适合防穿透:

  1. 占用内存很小。
  2. 能快速拦截大量不存在 ID。
  3. 不存在不会误判为一定存在,只会误判为可能存在。

缺点:

缺点说明
有误判可能把不存在判断为可能存在
删除困难多个元素可能共享 bit,不能随便清零
需要重建删除多或数据变化大时要定期重建

商业用法:

text
资产平台把有效 asset_id 加入布隆过滤器。请求先做参数校验,再查布隆过滤器;如果判断一定不存在,直接返回空;如果可能存在,再查 Redis 和 MySQL。新增资产成功后增量加入,删除较多时定期重建。

第七步:过期删除和内存淘汰

TTL 过期不代表 Redis 立刻删除 Key。Redis 结合惰性删除和定期删除。

mermaid
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

生产建议:

  1. 缓存 Key 尽量设置 TTL。
  2. 不要把 Redis 当无限内存数据库。
  3. 监控 used_memoryevicted_keys、命中率。
  4. 如果发生淘汰,要评估是否会导致数据库回源暴涨。

第八步:RDB、AOF 和数据丢失窗口

Redis 持久化不是强一致事务日志。

方式原理优点风险
RDB定期生成内存快照文件紧凑,恢复快两次快照之间可能丢数据
AOF追加写命令日志丢失窗口更小文件大,重放慢,需要 rewrite
混合持久化RDB 快照 + AOF 增量恢复兼顾速度和完整性仍要接受配置带来的丢失窗口

AOF appendfsync

配置含义
always每条命令尽量刷盘,可靠但慢
everysec每秒刷盘,常用,可能丢约 1 秒
no交给操作系统,性能好但风险大

缓存数据可以接受短暂丢失,因为能从 MySQL 重建;核心账本、订单、支付流水不能只靠 Redis 保存。

第九步:主从、哨兵和 Cluster

三者解决的问题不同。

架构解决什么不解决什么
主从副本和读扩展自动故障转移
哨兵主节点故障自动切换单主容量和写入上限
Cluster分片扩容和分片级高可用单 Key 热点、跨 slot 多 key 操作

哨兵故障转移:

mermaid
flowchart TD
    A["Sentinel 监控 Master"] --> B{"Master 主观/客观下线"}
    B -- "确认下线" --> C["选举 Sentinel Leader"]
    C --> D["选择一个 Slave 晋升"]
    D --> E["其他 Slave 复制新 Master"]
    E --> F["客户端更新连接"]

Cluster slot:

mermaid
flowchart TD
    A["Key"] --> B["CRC16 计算 slot"]
    B --> C["0-16383 slot"]
    C --> D["定位 Master 节点"]
    D --> E["执行命令"]

MOVED 和 ASK:

重定向含义
MOVEDslot 稳定迁到新节点,客户端应更新 slot 缓存
ASKslot 正在迁移,客户端临时去目标节点执行一次

Cluster 不能天然解决单个热 Key,因为一个 Key 只能落一个 slot。

第十步:大 Key 和热 Key

大 Key 是单个 value 或集合太大,热 Key 是访问太集中。

问题本质例子风险
大 Key单次操作太重一个 Hash 几十万 field阻塞、网络慢、删除慢、复制慢
热 Key单点流量太高秒杀商品库存单节点 CPU/网络打满

发现大 Key:

bash
redis-cli --bigkeys
redis-cli memory usage your:key
redis-cli slowlog get 20

治理:

问题方案
大 String拆字段、压缩、限制大小
大 Hash按业务维度拆分,用 HSCAN 分批
大 List/ZSet限制长度、分页、归档
删除大 KeyUNLINK,避免同步释放阻塞
热 Key本地缓存、多级缓存、拆 Key、限流、请求合并

本地缓存要看一致性要求。字典、配置、热门详情可以短 TTL 本地缓存;权限、库存、支付状态不要随便本地缓存。

第十一步:缓存和数据库一致性

常见写策略是:先更新数据库,事务提交后删除缓存。

mermaid
flowchart TD
    A["写请求"] --> B["更新 MySQL"]
    B --> C{"事务提交成功"}
    C -- "成功" --> D["删除 Redis 缓存"]
    D --> E{"删除是否成功"}
    E -- "成功" --> F["结束"]
    E -- "失败" --> G["记录重试/发送消息/CDC补偿"]
    C -- "失败" --> H["不动缓存"]

为什么不是先删缓存再更新数据库:

  1. 先删缓存后,还没更新数据库。
  2. 并发读发现缓存不存在,查到旧数据库。
  3. 并发读把旧值重新写入缓存。
  4. 写请求再更新数据库。
  5. 缓存里长期保留旧值。

删除缓存失败怎么办:

方案说明
重试表本地记录失败任务,后台重试
MQ 补偿删除失败发送消息异步处理
Binlog CDC订阅数据库变更,修正缓存
TTL 兜底即使删除失败,过期后能恢复

缓存通常只能做到最终一致。强一致读写要回数据库或业务服务。

第十二步:分布式锁生产写法

加锁:

bash
set lock:asset:A001 9f5c9b2a-uuid nx px 30000

释放锁必须用 Lua 判断 token:

lua
if redis.call("get", KEYS[1]) == ARGV[1] then
  return redis.call("del", KEYS[1])
else
  return 0
end

为什么不能直接 del lock

mermaid
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 的锁被误删

锁不是最终正确性的唯一保障。核心业务还要有:

  1. 数据库唯一约束。
  2. 幂等号。
  3. 状态机。
  4. Fencing Token。
  5. 超时补偿和重试。

第十三步:完整 Demo

资产详情缓存:

bash
set asset:detail:A001 '{"assetNo":"A001","status":"IDLE"}' ex 1800
get asset:detail:A001

热点排行榜:

bash
zadd asset:visit:rank 1 A001
zincrby asset:visit:rank 1 A001
zrevrange asset:visit:rank 0 9 withscores

限流计数:

bash
incr rate:api:asset:1001
expire rate:api:asset:1001 60

分布式锁:

bash
set lock:collect:task:1001 token-uuid nx ex 30

生产中这些命令不要孤立使用,要配合 TTL、异常处理、Lua、幂等和监控。

线上排查总流程

mermaid
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"]

常用命令:

bash
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 *阻塞 RedisSCAN 分批限速
大集合 HGETALL单次返回太大分页、拆 Key、HSCAN
缓存无 TTL内存长期占用、旧值难恢复设置合理 TTL
Redis 当唯一数据库故障或切换可能丢数据核心事实落 MySQL
直接 DEL 解锁可能误删别人锁token + Lua
Cluster 以为能解决所有热点单 Key 仍落单 slot本地缓存、拆 Key、限流
删除大 Key 用 DEL同步释放阻塞UNLINK
删除缓存失败不补偿长时间脏缓存重试、MQ、CDC、TTL

面试标准回答

Redis 怎么从零学到生产可用

text
Redis 要按数据结构、缓存模式、高并发问题、内存治理、持久化、高可用、分布式锁和生产排查这条线学习。先掌握 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog 适合什么场景,再理解 Redis 为什么快、为什么单线程下慢命令会阻塞。缓存使用 Cache Aside,要处理穿透、击穿、雪崩、TTL、空值缓存和布隆过滤器。生产还要理解 RDB/AOF、主从、哨兵、Cluster、大 Key、热 Key、缓存一致性、分布式锁和 timeout 排查。

Redis 为什么快,什么时候会慢

text
Redis 快主要因为数据在内存中,使用 IO 多路复用处理连接,核心命令执行模型简单,内置数据结构针对场景优化,很多命令复杂度低。但 Redis 不是永远快,大 Key、全量命令、长 Lua、复杂集合运算、AOF rewrite、主从同步、网络传输、连接池耗尽都会让它变慢。排查时要看 slowlog、commandstats、bigkeys、memory usage、连接池和应用回源情况。

Redis 和数据库如何保证一致性

text
常见 Cache Aside 策略是先更新数据库,事务提交后删除缓存。删除失败时要有重试、MQ、Binlog CDC 或 TTL 兜底。不要简单先删缓存再更新数据库,因为并发读可能在数据库更新前读到旧值并重新写入缓存。缓存和数据库通常只能做到最终一致,强一致场景要读数据库或通过业务状态机保证。

关联知识点

知识点说明
Redis 总览专栏入口和学习顺序
命令执行与数据结构原理为什么快、命令如何执行
过期删除与内存淘汰原理TTL、淘汰策略、内存治理
缓存问题穿透、击穿、雪崩
布隆过滤器原理防穿透的位数组和多哈希
主从哨兵 Cluster 全过程高可用和分片
大 Key 与热 Key 治理高并发热点治理
高并发缓存治理timeout、连接池、慢命令排查
分布式锁Redis 锁正确写法
Redis 面试标准回答和追问

本章小结

Redis 从零到生产级掌握,重点不是背命令,而是把数据结构、缓存模式、高并发问题、内存治理、持久化、高可用、锁和排查串起来。你要知道 Redis 能解决什么,也要知道它不能解决什么;知道为什么快,也知道什么时候会慢;知道怎么用,也知道出了问题怎么定位。