跳转到主要内容

数据库/协同工具类型

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 的核心区别:

MySQLRedis
数据放哪磁盘内存
怎么操作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 八种数据类型详解》)。选对结构是精髓

结构常用命令典型场景
StringSET GET INCR DECR SETNX缓存、计数、分布式锁
HashHSET HGET HGETALL HDEL HINCRBY存对象(用户/商品信息)
ListLPUSH RPUSH LPOP RPOP LRANGE队列、最新动态、分页
SetSADD SMEMBERS SINTER SUNION SDIFF去重、共同好友、标签
ZSetZADD 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:profilearticle: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一个键对应一个值
TTLkey 的存活时间,到期自动删
数据结构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 混合。

五、常见问题与排查

缓存穿透、击穿、雪崩三者表现都是「请求绕过缓存打到底层数据库」,但触发条件不同。一张图看清它们的区别与共同后果:

flowchart TB C["客户端请求"] P("穿透:查不存在的key") B("击穿:单个热点key过期") S("雪崩:大量key同时过期") D["DB 压力暴增"] C -->|"查不存在的数据"| P C -->|"热点key 过期瞬间"| B C -->|"大批key 同时过期"| S P --> D B --> D S --> D

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 --bigkeysMEMORY 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)数据分片 + 高可用大数据量、高并发(多分片分担)

主从复制是读写分离的基础,用图理解写主、读从的数据流向:

flowchart LR W["写请求"] R["读请求"] M["主库 Master"] S1["从库1"] S2["从库2"] W --> M R --> S1 R --> S2 M -->|"异步复制"| S1 M -->|"异步复制"| S2

哨兵在主从之上加了自动故障转移,主库挂掉后的完整时序:

sequenceDiagram autonumber participant M as 主库 Master participant S1 as 从库1 participant S2 as 从库2 participant SN as 哨兵集群 participant C as 客户端 Note over M: 主库宕机 M-->>SN: 心跳超时丢失 SN->>SN: 互相探测主观下线 SN->>SN: 投票达成客观下线 SN->>S1: 选为新主并提升 S2->>S1: 重新复制新主 SN-->>C: 通知新主地址 C->>S1: 读写切换到新主

难点:集群下跨槽操作受限(多 key 操作如 MGET、事务要求 key 在同一槽,用 hash tag {tag} 强制同槽)、数据倾斜(热 key / 大 key 落在一个分片)。

七、生产环境要点

  • 禁用线上 KEYS *:阻塞,用 SCAN 替代(游标式、不阻塞)。
  • maxmemory + 淘汰策略,否则内存吃满会 OOM 或被系统杀。
  • 开 AOF 持久化appendonly yes),别指望纯内存重启不丢。
  • 监控:内存使用率、命中率(INFO statskeyspace_hits/misses)、慢日志(SLOWLOG)、连接数。
  • TTL 必加随机,防雪崩。
  • 不要存大 value:单 value 控制在 10KB 内,超了拆分或换存储。

速查:常见问题对照

现象可能原因解决
大量请求打到 DB穿透 / 击穿 / 雪崩空值缓存 / 互斥锁 / TTL 加随机
Redis 响应变慢大 key / 慢命令 / 内存满拆大 key、禁 KEYS、查淘汰
写报 OOM内存满maxmemory + allkeys-lru
分布式锁误删没校验锁 ownervalue 设唯一 id + Lua 删
重启丢数据没开持久化开 AOF