数据库/协同工具类型
Redis 深度指南
Redis 的作用定位、何时该用、核心机制(单线程/数据结构/过期淘汰/持久化)、常见问题排查(缓存穿透击穿雪崩、大key、热key、内存满)、难点解决方案(缓存一致性、分布式锁、高可用集群)与生产要点。
- Redis
- 缓存
这是 Redis 的完整文档:先从零讲清 Redis 是什么,再带你跑起来、用命令玩通 5 种数据结构,最后是深度参考(作用定位、常见问题排查、难点方案)。在 Nest 应用里封装 RedisModule 见 Nest 课程第十三章。
一、基础入门(从零开始)
这一节从「Redis 是什么」讲到能用命令玩通 5 种数据结构,零基础也能跟上;Nest 课程那篇讲的是在应用里封装 RedisModule——两篇不重复。
1. 先搞懂:Redis 是什么
Redis(REmote DIctionary Server)是开源的内存型 key-value 数据库(菜鸟教程)。记住三个关键词:
- 内存:数据放在内存里,读写微秒级,极快。
- key-value:一个 key 对应一个 value,
SET name tom/GET name就是读写一个键。 - 数据结构服务器:value 不只是字符串,可以是 5 种结构(string / hash / list / set / zset),所以 Redis 常被称为「数据结构服务器」。
和 MySQL 的核心区别:
| MySQL | Redis | |
|---|---|---|
| 数据放哪 | 磁盘 | 内存 |
| 怎么操作 | SQL 语句 | 命令(SET/GET/HSET…) |
| 结构 | 表(行 + 列) | key-value(value 是数据结构) |
| 速度 | 毫秒级 | 微秒级 |
| 重启 | 数据还在 | 内存数据可能丢(靠持久化) |
一句话:MySQL 存「不能丢的真相」,Redis 存「要快、丢了能重建」的加速数据(缓存、计数、排行榜)。所以 Redis 一般不单独当主库,而是配合 MySQL 用。
2. Redis 怎么用:命令 + 5 种数据结构
Redis 没有 SQL、没有表。你直接用命令读写 key,一个 key 的 value 可以是 5 种数据结构之一:
| 结构 | value 长什么样 | 典型用途 |
|---|---|---|
| String | 一个值(字符串/数字) | 缓存、计数 |
| Hash | 字段-值 的映射(像一个对象) | 存用户/商品信息 |
| List | 有序列表 | 队列、最新动态 |
| Set | 无序、不重复的集合 | 去重、共同好友 |
| ZSet | 带分数的有序集合 | 排行榜 |
选哪种结构 = 让 value 是什么,结构决定了你能对它做什么操作。先把 Redis 跑起来,再把这 5 种各试一遍。
3. 用 Docker 起一个 Redis
docker run -d --name redis -p 6379:6379 redis:7
- 默认无密码;
docker logs redis看到Ready to accept connections就起来了。 - 应用连接地址:
localhost:6379。
4. 连上它
docker exec -it redis redis-cli
看到 127.0.0.1:6379> 提示符就连上了。
5. 最基础的几条命令
# ① String:缓存一个值
127.0.0.1:6379> SET hello world
OK
127.0.0.1:6379> GET hello
"world"
# ② 计数:原子自增(限流、阅读量用)
127.0.0.1:6379> INCR counter
(integer) 1
127.0.0.1:6379> INCR counter
(integer) 2
# ③ Hash:存对象
127.0.0.1:6379> HSET user:1 name tom age 20
(integer) 2
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "tom"
3) "age"
4) "20"
# ④ List:队列(左进、右出 = 先进先出)
127.0.0.1:6379> LPUSH queue a b c
(integer) 3
127.0.0.1:6379> RPOP queue
"c"
# ⑤ Set:去重
127.0.0.1:6379> SADD tags go nest
(integer) 2
127.0.0.1:6379> SMEMBERS tags
1) "go"
2) "nest"
# ⑥ ZSet:带分数,排行榜
127.0.0.1:6379> ZADD rank 100 alice 90 bob
(integer) 2
127.0.0.1:6379> ZRANGE rank 0 -1 REV WITHSCORES
1) "alice"
2) "100"
3) "bob"
4) "90"
精髓:选对数据结构——计数用 string、对象用 hash、队列用 list、去重用 set、排行用 zset。
6. 五大数据结构详解
Redis 的「数据类型」就是 5 种数据结构(掘金《Redis 八种数据类型详解》)。选对结构是精髓:
| 结构 | 常用命令 | 典型场景 |
|---|---|---|
| String | SET GET INCR DECR SETNX | 缓存、计数、分布式锁 |
| Hash | HSET HGET HGETALL HDEL HINCRBY | 存对象(用户/商品信息) |
| List | LPUSH RPUSH LPOP RPOP LRANGE | 队列、最新动态、分页 |
| Set | SADD SMEMBERS SINTER SUNION SDIFF | 去重、共同好友、标签 |
| ZSet | ZADD ZRANGE ZREVRANGE ZSCORE | 排行榜、优先级队列 |
选型口诀:计数用 String、对象用 Hash、队列用 List、去重用 Set、排行用 ZSet。别用 String 硬塞 JSON 当对象——用 Hash 更省内存、还能单独改一个字段。
还有 3 种特殊类型(用到再查):Bitmap(位图,签到/活跃统计)、HyperLogLog(去重计数,UV,有约 0.8% 误差)、GEO(地理位置,附近的人)。
7. 通用命令
对所有 key 都适用:
EXISTS key # key 是否存在
DEL key # 删除
TYPE key # 看类型
EXPIRE key 60 # 设 60 秒过期
TTL key # 剩余秒数(-1 永久、-2 不存在)
PERSIST key # 取消过期
KEYS * # 列出所有 key(只本机玩,生产禁用!会阻塞)
SCAN 0 # 生产用 SCAN 游标式遍历,不阻塞
SELECT 0 # 切换库(默认 0,共 16 个)
DBSIZE # 当前库 key 数量
FLUSHDB # 清当前库(危险)
8. 基本使用规则
- key 命名用冒号分层:
user:1001:profile、article:88:likes,清晰又好管理。 - 凡是缓存都设 TTL,防永久堆积;TTL 加点随机值防雪崩(见深度章节)。
- 生产禁用
KEYS *,改用SCAN(不阻塞单线程)。 - 选对结构:对象用 Hash 不用 String 塞 JSON;排行用 ZSet 不用 List 硬排。
- 单 value 控制大小(< 10KB),大 hash/list 拆分,避免阻塞单线程。
- 分布式锁用
SET key val NX EX 30(NX 抢锁、EX 防死锁),删锁前校验 owner(见深度章节)。
9. 核心词汇速记
| 术语 | 一句话 |
|---|---|
| key / value | 一个键对应一个值 |
| TTL | key 的存活时间,到期自动删 |
| 数据结构 | string/hash/list/set/zset 五种 |
| 持久化 | 内存数据落盘(RDB/AOF),防重启丢失 |
| 单线程 | 命令排队执行,所以天然原子、也怕慢命令阻塞 |
二、作用与定位
Redis 是内存型 key-value 数据库,核心价值是快(内存读写、单线程无锁竞争、微秒级响应)。典型用途:
- 缓存:挡在 MySQL 前面,热点数据读 Redis 不读库。
- 计数 / 限流:
INCR原子计数(阅读量、点赞、API 限流)。 - 分布式锁:多实例互斥(
SETNX+ 过期)。 - 排行榜 / 关注关系:zset / set 的天然能力。
- 消息:简单的发布订阅、延迟队列。
一句话:对速度敏感、可重建、能接受短暂不一致的数据,放 Redis。
三、何时该用 / 何时别用
| 场景 | 用不用 Redis |
|---|---|
| 热点读、降低 DB 压力 | 用(缓存) |
| 高频计数、限流 | 用 |
| 分布式锁、会话 | 用 |
| 钱袋、订单这种不能丢、强一致数据 | 别用(用 MySQL) |
| 关系复杂、要 JOIN | 别用(Redis 没有关系能力) |
Redis 不适合当主数据库:内存贵(不能存海量)、持久化是异步的(重启可能丢最近数据)、事务弱。MySQL 是真相源,Redis 是可重建的加速层。
四、核心机制速览
- 单线程 + IO 多路复用:命令顺序执行,无并发竞争,所以
INCR这类天然原子。慢命令(KEYS *、大SORT)会阻塞所有请求。 - 5 种核心数据结构:string(缓存/计数)、hash(对象)、list(队列)、set(去重/关系)、zset(排行榜)。选对结构是 Redis 的精髓——用对结构比用 string 硬塞高效得多。
- 过期 + 淘汰:key 设 TTL 过期;内存满了按策略淘汰(默认
noeviction报错,生产常设allkeys-lru淘汰最久未用)。 - 持久化:RDB(快照,周期落盘,重启快、可能丢数据)和 AOF(追加每条写命令,更安全、文件大)。生产常开 AOF + RDB 混合。
五、常见问题与排查
缓存穿透、击穿、雪崩三者表现都是「请求绕过缓存打到底层数据库」,但触发条件不同。一张图看清它们的区别与共同后果:
1. 缓存穿透(查不存在的数据)
现象:大量请求查一个 DB 里根本不存在的 key(比如恶意攻击 id=-1),每次都穿透到 DB。
方案:
- 查 DB 没结果也缓存空值(
SET key "" EX 60),下次直接返回空。 - 用 布隆过滤器:请求先过布隆过滤器,不存在的直接拒,不查 DB。
2. 缓存击穿(热点 key 过期)
现象:一个超高并发访问的热点 key 突然过期,瞬间所有请求打到 DB。
方案:
- 互斥锁重建:只有一个请求去查 DB 回填缓存,其他等(
SETNX抢锁)。 - 热点 key 不设过期,靠后台定时更新。
- 物理永不过期 + 逻辑过期:value 里带过期时间,发现过期后异步刷新。
3. 缓存雪崩(大量 key 同时过期)
现象:大批 key 同一时刻过期(或 Redis 整个挂了),请求全打到 DB。
方案:
- TTL 加随机偏移(
EX 3600 + random(0,300)),避免同时过期。 - 多级缓存(本地缓存 + Redis)。
- Redis 高可用(哨兵/集群,见下文),别让 Redis 整个挂。
4. 大 key(单个 key 的 value 特别大)
危害:读写大 key 阻塞单线程、网络传输慢、集群迁移卡顿、淘汰时引发抖动。
排查:redis-cli --bigkeys 或 MEMORY USAGE key。
方案:拆分——大 hash 按字段拆、大 list/zset 分片成多个、大 string 拆成多个 key;或迁移到别处(文件存 OSS)。
5. 热 key(单个 key 访问量极高)
危害:单分片 CPU 打满(集群里热 key 落在一个分片)。
方案:
- 多副本读:同一个数据写多个 key(
hot_0/hot_1/…),读时随机选一个,分散到不同分片。 - 本地缓存(进程内 LRU),减少对 Redis 的访问。
6. 内存满 / OOM
现象:OOM command not allowed,写不进去。
方案:设 maxmemory + 合理淘汰策略(allkeys-lru);排查并清理大 key / 无 TTL 的 key。
六、难点与解决方案
1. 缓存与数据库一致性
写数据时要同时改 DB 和缓存,怎么保证两边一致?常见策略:
- Cache Aside(旁路缓存,最常用):写时先更新 DB,再删缓存(不是更新缓存)。读时缓存没就查 DB 回填。为什么是「删」不是「更新」:避免并发写时缓存被旧值覆盖、且懒加载省内存。
- 延迟双删:写 DB 前删一次缓存、写完 DB 后延迟再删一次(清掉读旧值期间回填的脏数据)。
- 最终一致:通过 binlog(Canal)监听 DB 变更异步刷缓存,业务代码不感知。
接受短暂不一致是缓存系统的常态。强一致场景不要用缓存(直接读 DB)。
2. 分布式锁
SET key value NX EX 30 抢锁(NX 保证只有第一个成功、EX 防死锁)。难点:
- 锁被别人误删:A 超时未释放,B 抢到锁,A 干完把 B 的锁删了。→ value 设唯一 id,删锁前用 Lua 脚本判断「是不是自己的」。
- 业务执行超时锁过期:开看门狗(定时续期),如 Redisson 实现。
- 单点故障:单 Redis 挂了锁失效。→ 用 Redlock(向多个独立 Redis 节点抢锁,多数成功才算成功),但有争议,强一致需求建议上 Zookeeper/etcd。
3. 高可用:主从 / 哨兵 / 集群
| 方案 | 能力 | 适用 |
|---|---|---|
| 主从复制 | 读写分离、数据备份 | 读多写少、能接受手动切主 |
| 哨兵(Sentinel) | 主从 + 自动故障转移 | 中小规模、自动高可用 |
| 集群(Cluster) | 数据分片 + 高可用 | 大数据量、高并发(多分片分担) |
主从复制是读写分离的基础,用图理解写主、读从的数据流向:
哨兵在主从之上加了自动故障转移,主库挂掉后的完整时序:
难点:集群下跨槽操作受限(多 key 操作如 MGET、事务要求 key 在同一槽,用 hash tag {tag} 强制同槽)、数据倾斜(热 key / 大 key 落在一个分片)。
七、生产环境要点
- 禁用线上
KEYS *:阻塞,用SCAN替代(游标式、不阻塞)。 - 设
maxmemory+ 淘汰策略,否则内存吃满会 OOM 或被系统杀。 - 开 AOF 持久化(
appendonly yes),别指望纯内存重启不丢。 - 监控:内存使用率、命中率(
INFO stats的keyspace_hits/misses)、慢日志(SLOWLOG)、连接数。 - TTL 必加随机,防雪崩。
- 不要存大 value:单 value 控制在 10KB 内,超了拆分或换存储。
速查:常见问题对照
| 现象 | 可能原因 | 解决 |
|---|---|---|
| 大量请求打到 DB | 穿透 / 击穿 / 雪崩 | 空值缓存 / 互斥锁 / TTL 加随机 |
| Redis 响应变慢 | 大 key / 慢命令 / 内存满 | 拆大 key、禁 KEYS、查淘汰 |
| 写报 OOM | 内存满 | 设 maxmemory + allkeys-lru |
| 分布式锁误删 | 没校验锁 owner | value 设唯一 id + Lua 删 |
| 重启丢数据 | 没开持久化 | 开 AOF |