Skip to content

Redis 系统学习文档 ​

资料基准:本文以 Redis 官方最新文档为主要依据整理,覆盖数据结构、命令、缓存设计、事务、Lua/Functions、持久化、复制、Sentinel、Cluster、内存淘汰与工程化实践。适合后端开发者从入门到高级系统学习。

0. 学习目标与路线 ​

0.1 Redis 是什么 ​

Redis 是一个以内存为主的数据结构服务器,常被用作缓存、分布式协调、排行榜、计数器、消息流、会话存储和实时数据处理组件。它不是只能存字符串的“简单 KV”,而是围绕多种数据结构提供高性能命令。

0.2 分层学习地图 ​

阶段目标必学知识
基础会连接、会读写、会设置过期时间String、Hash、List、Set、Sorted Set、Key、TTL
进阶能设计缓存并避免常见事故缓存穿透/击穿/雪崩、Pipeline、事务、Lua、慢查询、大 Key
高级能理解高可用和持久化RDB、AOF、复制、Sentinel、Cluster、内存淘汰
工程化能稳定上线和排障监控、容量评估、客户端连接、ACL、备份、故障演练

0.3 学习前置 ​

建议先具备:

  • 基本数据结构知识。
  • TCP/网络延迟基础。
  • 数据库索引和事务基础。
  • Java、Node.js、Python 或任一后端语言的客户端使用经验。
  • Linux 基础命令和服务部署经验。

1. Redis 的核心特性 ​

1.1 为什么 Redis 快 ​

常见原因:

  • 数据主要存放在内存中。
  • 命令执行模型简洁,单个命令通常很快。
  • 高效的数据结构实现。
  • 网络协议简单。
  • 支持 Pipeline 减少往返延迟。
  • 支持 I/O 多路复用。

但 Redis 快不代表任何命令都快。KEYS、大范围 LRANGE、超大集合运算、大 Key 删除等都可能阻塞或拖慢服务。

1.1.1 内部机制:单线程事件模型 ​

Redis 的「单线程」指命令执行是单线程:主线程通过 epoll/kqueue/select 构成的事件循环(I/O 多路复用)接收请求,把命令放入队列后逐条原子执行。这带来三个推论:

  • 命令天然串行,不存在执行期数据竞争,也因此无需为单条命令加锁。
  • 一条慢命令会推迟它后面所有命令——这是「禁止 KEYS *、警惕 O(N) 命令」的根本原因。
  • 并非所有工作都在主线程:RDB/AOF rewrite 通过 fork() 子进程完成,AOF fsync、UNLINK 的惰性释放、部分 I/O 线程(io-threads,默认关闭)在后台线程执行。

每个 key 的存储核心是逻辑库(默认 16 个,databases 配置)中的全局哈希表(dict),底层为 SDS 字符串到对象指针的映射。Redis 对象带有空闲时钟(LRU/LFU 元数据),供过期与淘汰使用。

💡 常见内部编码(可用 OBJECT ENCODING key 查看):String 用 int(可表示为长整数的共享值)或 embstr/raw;小 Hash/ZSet/Set 用 listpack(7.0 起取代 ziplist)节省内存,超阈值退化为 hashtable/skiplist;List 用 quicklist(双向链表串起多个 listpack 节点);Stream 用基数树(Rax)挂 listpack 节点。默认小集合阈值如 hash-max-listpack-entries 512、zset-max-listpack-entries 128(8.4 redis.conf 默认值),超出后内存占用会跳变。

1.2 Redis 适合什么 ​

适合:

  • 热点数据缓存。
  • 分布式会话。
  • 计数器、限流器。
  • 排行榜。
  • 去重集合。
  • 延迟不高的消息流。
  • 分布式锁或协调原语。
  • 实时统计。

不适合:

  • 替代关系型数据库承担复杂事务。
  • 存储无法丢失且没有备份的核心账务数据。
  • 盲目存放超大对象。
  • 需要复杂 ad-hoc 查询的场景。

2. 安装、连接与基础命令 ​

2.1 redis-cli ​

bash
redis-cli -h 127.0.0.1 -p 6379

认证:

bash
AUTH default your_password

选择数据库:

bash
SELECT 0

生产中不建议依赖多个逻辑库隔离复杂业务。更清晰的方式是使用独立实例、不同前缀、不同账号权限或不同集群。

2.2 Key 命名 ​

推荐:

text
业务:实体:标识:字段
user:profile:1001
user:token:1001
order:detail:202606250001
rank:daily:2026-06-25

原则:

  • 可读但不要过长。
  • 使用统一分隔符,如冒号。
  • 包含业务域和实体类型。
  • 避免无意义短 key,如 a1。
  • Cluster 中需要同槽多 key 操作时使用 hash tag,如 cart:{1001}:items。

2.3 常用 Key 命令 ​

bash
EXISTS user:1001
TYPE user:1001
DEL user:1001
UNLINK user:1001
EXPIRE user:1001 3600
TTL user:1001
PERSIST user:1001
SCAN 0 MATCH user:* COUNT 100

DEL 同步删除,删除大 Key 可能阻塞;UNLINK 可异步释放内存,更适合大对象删除。

2.4 不要在线上使用 KEYS 扫全库 ​

bash
KEYS user:*

KEYS 会遍历整个 keyspace,在线上大实例可能造成阻塞。生产排查优先使用 SCAN 渐进式遍历。

bash
SCAN 0 MATCH user:* COUNT 1000

注意:SCAN 可能返回重复 key,业务侧要能容忍。

3. String ​

3.1 基础命令 ​

bash
SET user:name:1001 "Alice"
GET user:name:1001
MGET user:name:1001 user:name:1002
SET user:code:1001 "938201" EX 300   # 带过期时间(SETEX 已废弃,统一用 SET ... EX)
SET user:token:1001 "abc" EX 3600 NX

3.2 计数器 ​

bash
INCR article:1001:views
INCRBY article:1001:views 10
DECR stock:sku1001

INCR 是原子操作,适合访问量、库存预扣、限流计数。但库存最终一致性还需要数据库校验和补偿。

3.3 分布式锁基础写法 ​

bash
SET lock:order:1001 random-token NX PX 30000

释放时必须校验 token,避免删掉别人的锁。Redis 8.4 起官方文档提到可用 DELEX key IFEQ value;低版本可用 Lua 脚本比较 value 后删除。

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

3.4 String 使用注意 ​

  • 单个 value 不要过大。
  • 需要局部字段更新时考虑 Hash。
  • JSON 字符串适合读写整体对象,不适合频繁局部更新。
  • 计数器要考虑溢出、重置和补偿。

4. Hash ​

4.1 基础命令 ​

bash
HSET user:1001 name "Alice" email "alice@example.com" age 20
HGET user:1001 name
HMGET user:1001 name email
HGETALL user:1001
HINCRBY user:1001 loginCount 1
HDEL user:1001 age

4.2 适合场景 ​

  • 用户资料。
  • 商品摘要。
  • 配置项。
  • 小对象的局部字段更新。

4.3 Hash 与 String JSON 的选择 ​

方案优点缺点
Hash局部读写方便,字段级计数方便对复杂嵌套结构不友好
String JSON结构贴近业务对象,序列化简单局部更新成本高,字段级命令不方便

经验:

  • 对象字段少且需要局部更新,选 Hash。
  • 对象结构复杂且总是整体读写,选 String JSON。
  • Redis Stack 的 JSON 是另一类能力,和 Redis Open Source 基础类型要区分。

5. List ​

5.1 基础命令 ​

bash
LPUSH queue:email job1 job2
RPOP queue:email
LRANGE queue:email 0 9
LLEN queue:email
BLPOP queue:email 5

5.2 使用场景 ​

  • 简单队列。
  • 最新列表。
  • 时间线片段。
  • 固定长度日志。

5.3 固定长度列表 ​

bash
LPUSH recent:articles article:1001
LTRIM recent:articles 0 99

5.4 List 队列注意 ​

List 可做简单队列,但不具备完整消息系统能力。需要消费者组、消息确认、重试、历史追踪时,优先考虑 Stream 或专业消息队列。

6. Set ​

6.1 基础命令 ​

bash
SADD article:1001:likes user:1 user:2
SISMEMBER article:1001:likes user:1
SCARD article:1001:likes
SREM article:1001:likes user:2
SMEMBERS article:1001:likes

6.2 集合运算 ​

bash
SINTER tag:java tag:redis
SUNION tag:java tag:database
SDIFF followers:alice followers:bob

6.3 使用场景 ​

  • 去重。
  • 标签集合。
  • 用户关系。
  • 抽奖候选。
  • 共同关注。

注意:大集合的交并差计算可能成本很高,生产中要控制集合大小或做离线预计算。

7. Sorted Set ​

7.1 基础命令 ​

bash
ZADD rank:daily 100 user:1 95 user:2
ZREVRANGE rank:daily 0 9 WITHSCORES
ZRANK rank:daily user:2
ZREVRANK rank:daily user:1
ZINCRBY rank:daily 10 user:2
ZREM rank:daily user:1

7.2 使用场景 ​

  • 排行榜。
  • 延迟任务时间轮。
  • 带分数的优先级队列。
  • 按时间排序的事件集合。

7.3 排行榜设计 ​

日榜:

text
rank:article:daily:2026-06-25

周榜:

text
rank:article:weekly:2026-W26

建议:

  • 根据业务周期拆 key,避免单个排行榜无限增长。
  • 定期设置过期时间。
  • 分数相同时要明确排序规则。
  • 超大排行榜分页要评估成本。

8. Bitmap、Bitfield 与 HyperLogLog ​

8.1 Bitmap ​

Bitmap 本质上是 String 的位操作能力。

bash
SETBIT sign:2026-06 userIdOffset 1
GETBIT sign:2026-06 userIdOffset
BITCOUNT sign:2026-06

适合:

  • 用户签到。
  • 活跃标记。
  • 布尔状态集合。
  • 大量 yes/no 统计。

前提:能把业务 ID 映射到合理 offset。

8.2 Bitfield ​

BITFIELD 可把字符串当作多个整数位段操作,适合紧凑计数,但可读性和维护成本较高。

8.3 HyperLogLog ​

bash
PFADD uv:2026-06-25 user:1 user:2
PFCOUNT uv:2026-06-25
PFMERGE uv:2026-06 uv:2026-06-24 uv:2026-06-25

适合估算 UV、独立访客、唯一元素数量。它是概率结构,有误差,不适合需要精确去重明细的场景。

9. Stream ​

9.1 基础命令 ​

bash
XADD orders * userId 1001 amount 99.9
XRANGE orders - +
XREAD COUNT 10 BLOCK 5000 STREAMS orders $

9.2 消费者组 ​

bash
XGROUP CREATE orders order-service $
XREADGROUP GROUP order-service c1 COUNT 10 STREAMS orders >
XACK orders order-service 1690000000000-0

9.3 Stream 适合什么 ​

  • 需要消息 ID。
  • 需要消费者组。
  • 需要确认和 pending 列表。
  • 需要保留一段历史。
  • 需要多个消费者协作消费。

9.4 Stream 与消息队列的边界 ​

Stream 很强,但不是所有场景都能替代 Kafka、RabbitMQ 等专业消息系统。以下场景要慎重:

  • 极高吞吐和长期历史存储。
  • 复杂路由。
  • 多数据中心强治理。
  • 严格消息审计。

10. Geospatial ​

10.1 基础命令 ​

bash
GEOADD shop:geo 116.397 39.908 shop:1
GEOSEARCH shop:geo FROMLONLAT 116.397 39.908 BYRADIUS 5 km WITHDIST

适合:

  • 附近门店。
  • 附近司机。
  • 地理位置粗筛。

注意:

  • Redis GEO 基于 Sorted Set。
  • 更复杂的地理搜索、路径规划、空间索引应交给专业系统。

11. TTL 与过期策略 ​

11.1 设置过期 ​

bash
EXPIRE user:token:1001 3600
PEXPIRE user:token:1001 60000
TTL user:token:1001
PTTL user:token:1001
SET user:code:1001 938201 EX 300

返回值理解:

  • TTL 返回正数:剩余秒数。
  • TTL 返回 -1:key 存在但没有过期时间。
  • TTL 返回 -2:key 不存在。

11.2 过期时间设计 ​

建议:

  • 缓存 key 必须有 TTL,除非它是明确的持久数据。
  • 给同类缓存加随机抖动,避免同时失效。
  • 短 TTL 适合变化快的数据。
  • 长 TTL 适合变化慢且可主动失效的数据。
  • 登录 token、验证码、临时锁必须设置过期。

11.3 TTL 抖动 ​

java
int ttl = 3600 + ThreadLocalRandom.current().nextInt(0, 300);

避免大量 key 在同一秒过期造成缓存雪崩。

11.4 过期删除的内部机制 ​

Redis 不会为每个 key 建定时器,而是惰性删除 + 定期删除组合:

机制行为代价
惰性删除(lazy)访问 key 时才检查是否过期,过期则删除CPU 换内存:无人访问的过期 key 长期占内存
定期删除(active)后台 serverCron(默认每 100ms,hz 10)从过期字典随机抽样 20 个 key,删除其中已过期的;若过期比例超过 25% 则继续同轮扫描,直到低于阈值或超时预算用完概率性、近似;单个扫描周期有 CPU 上限

⚠️ 由此推出两个事实:过期 key 的内存释放时间不确定(所以 DBSIZE、内存曲线会「滞后」);大量 key 同时到期时,定期删除会抬高主线程延迟,这就是 TTL 抖动不仅防雪崩、也防「过期风暴」的原因。内存达到 maxmemory 时另有独立的**淘汰(eviction)**流程,见 21 节,两者不要混淆:过期是「兑现 TTL 承诺」,淘汰是「容量压力下的取舍」。

12. 缓存设计 ​

12.1 Cache Aside ​

最常见模式:

  1. 读缓存。
  2. 命中则返回。
  3. 未命中则读数据库。
  4. 写入缓存并设置 TTL。
  5. 返回数据。

伪代码:

java
User user = redis.get(key);
if (user == null) {
    user = userRepository.findById(id);
    redis.set(key, user, ttl);
}
return user;

12.2 更新策略 ​

常见策略:

  • 写数据库后删除缓存。
  • 写数据库后更新缓存。
  • 延迟双删。
  • 使用消息队列异步失效。

多数业务优先选择“写数据库后删除缓存”,因为更新缓存容易出现并发覆盖旧值。

12.3 缓存穿透 ​

问题:查询不存在的数据,每次都打到数据库。

解决:

  • 缓存空值,设置短 TTL。
  • 参数校验过滤非法 ID。
  • Bloom Filter 预判是否可能存在。
  • 限流异常请求。

12.4 缓存击穿 ​

问题:热点 key 过期瞬间,大量请求打到数据库。

解决:

  • 热点 key 不过期,后台刷新。
  • 互斥锁重建缓存。
  • 逻辑过期。
  • 请求合并。

12.5 缓存雪崩 ​

问题:大量 key 同时失效或 Redis 故障,流量压垮数据库。

解决:

  • TTL 加随机抖动。
  • 分批预热。
  • 多级缓存。
  • 限流降级。
  • Redis 高可用。
  • 数据库保护策略。

12.6 缓存一致性 ​

需要先问:

  • 允许多长时间不一致?
  • 是读多写少还是写多读多?
  • 是否存在多服务写同一数据?
  • 是否需要写后读强一致?
  • 是否有消息队列或 binlog 订阅?

常见经验:

  • 一般缓存追求最终一致。
  • 强一致读直接读数据库或主库。
  • 写后马上读可删除缓存并读数据库。
  • 对核心资金类数据不要只依赖缓存。

12.7 「先更新库再删缓存」的竞态分析 ​

Cache Aside 被选为默认策略,但并发下仍有窗口,值得理解:

text
时刻 1:读事务 A 未命中缓存,读到数据库旧值 v1
时刻 2:写事务 B 更新数据库为 v2,并删除缓存
时刻 3:A 把旧值 v1 写回缓存 —— 缓存停留在 v1,直到 TTL 到期

缓解手段(按成本排序):

  • 给缓存设 TTL:不一致窗口有上界,多数业务可接受。
  • 延迟双删:写库删缓存后,延迟数百毫秒再删一次,覆盖并发回填;延迟时长要大于「读库+回填」的耗时。
  • 订阅 binlog 失效缓存(Canal、Debezium 等):把「删缓存」从业务代码移到异步管道,天然带重试,一致性最好,运维成本也最高。
  • 版本号/时间戳比较:回填时拒绝比当前缓存更旧的值。

💡 为什么「更新缓存」不如「删除缓存」:更新缓存在并发写下可能出现后写先到的乱序覆盖;删除对到达顺序不敏感(幂等),让下一次读重建即可。

13. Pipeline ​

13.1 Pipeline 解决什么 ​

Pipeline 把多个命令连续发给 Redis,减少网络往返时间。它不保证这些命令作为事务原子执行。

示例:

text
SET k1 v1
SET k2 v2
GET k1
GET k2

客户端可一次写入多个请求,再批量读取响应。

13.2 适合场景 ​

  • 批量读取多个 key。
  • 批量写入简单命令。
  • 批量计数。
  • 初始化或迁移数据。

13.3 注意事项 ​

  • Pipeline 太大可能占用客户端和服务端内存。
  • 命令仍按顺序执行,但中途错误要按响应逐条处理。
  • 不等于事务,不具备回滚能力。
  • Cluster 下多 key 分散到不同节点时客户端要支持拆分。

14. 事务 ​

14.1 MULTI / EXEC ​

bash
MULTI
INCR account:1
DECR account:2
EXEC

MULTI 后命令进入队列,EXEC 时按顺序执行。Redis 事务不等于关系型数据库事务,它没有传统回滚机制。

14.2 DISCARD ​

bash
MULTI
SET k1 v1
DISCARD

丢弃队列中的命令。

14.3 WATCH 乐观锁 ​

bash
WATCH stock:sku1001
GET stock:sku1001
MULTI
DECR stock:sku1001
EXEC

如果被 WATCH 的 key 在 EXEC 前被其他客户端修改,事务会失败,客户端应重试或返回失败。

14.4 事务使用建议 ​

  • 简单原子操作优先用单条命令,如 INCR、HINCRBY。
  • 复杂原子逻辑优先考虑 Lua 或 Redis Functions。
  • 事务不适合做长业务流程。
  • 不要把 Redis 事务理解为数据库 ACID 事务。

15. Lua 脚本与 Redis Functions ​

15.1 Lua 的价值 ​

Lua 脚本能把多个 Redis 操作放到服务端一次执行,减少网络往返并保证脚本执行期间的原子性。

示例:限流。

lua
local current = redis.call("INCR", KEYS[1])
if current == 1 then
  redis.call("EXPIRE", KEYS[1], ARGV[1])
end
return current

15.2 EVAL ​

bash
EVAL "return redis.call('INCR', KEYS[1])" 1 counter

脚本要遵守:

  • Key 通过 KEYS 传入。
  • 参数通过 ARGV 传入。
  • 不在脚本中动态访问未声明 key。
  • 脚本不能执行太久。

15.3 Redis Functions ​

Redis Functions 是比临时 Lua 脚本更工程化的服务端函数机制。函数可以加载到 Redis 中,使用 FCALL 调用,更适合复用和版本管理。

使用建议:

  • 简单一次性逻辑可用 Lua。
  • 多处复用、需要部署管理的逻辑考虑 Functions。
  • 对脚本执行耗时做严格限制。
  • 所有脚本都要有测试。

16. Pub/Sub ​

16.1 基础命令 ​

bash
SUBSCRIBE news
PUBLISH news "hello"

16.2 特点 ​

  • 发布即广播。
  • 订阅者在线才能收到。
  • 不保存历史消息。
  • 没有消息确认。

适合:

  • 实时通知。
  • 配置变更广播。
  • 简单事件传播。

不适合:

  • 需要持久化消息。
  • 需要可靠消费。
  • 消费者离线后要补消息。

这些场景优先考虑 Stream 或消息队列。

17. 持久化 ​

17.1 RDB ​

RDB 是某个时间点的数据快照。

优点:

  • 文件紧凑。
  • 适合备份和灾难恢复。
  • 重启加载速度通常较快。

缺点:

  • 两次快照之间的数据可能丢失。
  • 生成快照会有 fork 和写时复制成本。

适合:

  • 允许分钟级数据丢失。
  • 需要周期备份。
  • 缓存型实例。

17.2 AOF ​

AOF 记录写命令日志,重启时重放恢复。

常见 fsync 策略:

  • always:每次写都同步,最安全但最慢。
  • everysec:每秒同步,常用折中。
  • no:交给操作系统,性能好但风险高。

优点:

  • 数据丢失窗口通常小于 RDB。
  • 日志可读性较好。

缺点:

  • 文件可能更大。
  • 需要 rewrite 压缩。
  • 重放耗时可能较长。

17.3 RDB + AOF ​

很多生产场景会同时开启 RDB 和 AOF,兼顾备份与更小的数据丢失窗口。具体取舍要看数据价值、写入量、磁盘性能和恢复时间目标。

17.4 无持久化 ​

如果 Redis 只作纯缓存,可以关闭持久化。但要明确:

  • 重启后数据全部丢失。
  • 需要缓存预热或降级方案。
  • 不能存放关键唯一数据。

18. 复制 ​

18.1 主从复制 ​

Redis 复制通常是一个主节点接收写入,副本节点复制数据。

用途:

  • 读扩展。
  • 数据冗余。
  • 高可用基础。
  • 备份节点。

18.2 复制特点 ​

  • Redis 复制是异步的。
  • 主节点写入成功不代表副本已经收到。
  • 故障切换时可能丢失最近写入。
  • 副本可用于读,但要接受复制延迟。

18.3 读写分离注意 ​

  • 写后立即读要走主节点或等待复制确认。
  • 副本读适合对一致性不敏感的数据。
  • 读扩展前先评估热点 key 和网络瓶颈。

19. Sentinel ​

19.1 Sentinel 解决什么 ​

Sentinel 用于非 Cluster 架构下的高可用,负责:

  • 监控主节点和副本。
  • 发现主观/客观下线。
  • 自动故障转移。
  • 通知客户端新的主节点。

19.2 适合场景 ​

  • 单主多从。
  • 不需要自动分片。
  • 数据量单机可承载。
  • 希望主节点故障时自动切换。

19.3 注意事项 ​

  • Sentinel 本身也要部署多个实例。
  • 客户端必须支持 Sentinel 发现。
  • 故障切换期间可能短暂不可用。
  • 异步复制导致故障时可能丢数据。

20. Cluster ​

20.1 Cluster 解决什么 ​

Redis Cluster 提供数据分片和一定的高可用能力。它把 keyspace 分为 16384 个 hash slot,每个主节点负责一部分 slot。

20.2 hash slot ​

普通 key:

text
user:1001

Redis 根据 key 计算 slot。

使用 hash tag:

text
cart:{1001}:items
cart:{1001}:meta

花括号内相同内容会映射到同一个 slot,便于多 key 操作。

20.3 Cluster 下的限制 ​

  • 多 key 命令要求 key 在同一 slot。
  • 事务、Lua 多 key 也受 slot 限制。
  • 客户端需要处理 MOVED/ASK 重定向。
  • 跨 slot 聚合要在应用层完成。

20.4 什么时候用 Cluster ​

适合:

  • 数据量超过单机内存。
  • 单机 QPS 或网络达到瓶颈。
  • 需要横向扩展。

不适合:

  • key 设计混乱且大量跨 key 操作。
  • 数据量很小,只需要主从高可用。
  • 客户端或运维能力不足。

21. 内存管理与淘汰策略 ​

21.1 maxmemory ​

conf
maxmemory 8gb
maxmemory-policy allkeys-lru

maxmemory 限制 Redis 可用于数据集的内存。当写入导致超限,Redis 会根据淘汰策略处理。

21.2 常见淘汰策略 ​

策略含义适合场景
noeviction不淘汰,写入超限时报错不能接受自动删除
allkeys-lru从所有 key 中淘汰近似最久未使用通用缓存
allkeys-lrm从所有 key 中淘汰近似最久未修改Redis 8.6+,希望保留频繁读取但仍在维护的数据
volatile-lru从设置 TTL 的 key 中淘汰近似最久未使用混合持久 key 和缓存 key
allkeys-lfu从所有 key 中淘汰近似低频访问热点稳定场景
volatile-lfu从 TTL key 中淘汰低频访问混合场景
allkeys-random从所有 key 中随机淘汰访问分布均匀
volatile-random从 TTL key 中随机淘汰TTL key 访问分布较均匀
volatile-lrm从 TTL key 中淘汰近似最久未修改Redis 8.6+,混合实例中的修改时效型缓存
volatile-ttl优先淘汰剩余 TTL 更短的 keyTTL 能表达价值

LRM 是 Least Recently Modified,和 LRU 的区别是它关注“最近修改”而不是“最近访问”。使用前要确认你的 Redis 版本是否支持。

21.3 选择建议 ​

  • 纯缓存实例:常用 allkeys-lru 或 allkeys-lfu。
  • 混合持久数据和缓存:优先拆实例;不能拆时可考虑 volatile-*。
  • 核心数据不允许被删:使用 noeviction 并做好容量告警。

21.4 大 Key ​

大 Key 风险:

  • 网络传输慢。
  • 删除阻塞。
  • 迁移和复制压力大。
  • Cluster slot 倾斜。

排查:

bash
redis-cli --bigkeys
MEMORY USAGE key

治理:

  • 拆分 key。
  • 控制集合长度。
  • 使用 UNLINK 删除。
  • 异步清理。
  • 为列表、排行榜按时间或业务维度分片。

22. 性能优化 ​

22.1 关注复杂度 ​

每条 Redis 命令都有时间复杂度。不要只记命令名,要查命令复杂度。

高风险命令:

  • KEYS
  • SMEMBERS 大集合
  • HGETALL 大 Hash
  • LRANGE 超大范围
  • SUNION / SINTER 大集合运算
  • 删除大 Key

22.2 网络优化 ​

  • 使用连接池或连接复用。
  • 批量操作用 Pipeline。
  • 不要每个请求新建连接。
  • 客户端和 Redis 尽量部署在低延迟网络。
  • 控制 value 大小。

22.3 热 Key ​

热 Key 表现:

  • 单 key QPS 极高。
  • 某个分片 CPU 或网络打满。
  • Cluster 下访问倾斜。

治理:

  • 本地缓存。
  • 热点 key 拆分。
  • 读副本扩展。
  • 限流。
  • 对热点数据预计算。

22.4 慢查询 ​

bash
SLOWLOG GET 10
SLOWLOG LEN

慢查询只统计命令执行时间,不包含网络传输和排队时间。还应结合:

  • INFO
  • LATENCY DOCTOR
  • 客户端耗时日志
  • 系统 CPU、内存、磁盘、网络

23. 安全 ​

23.1 认证与 ACL ​

Redis 支持用户和 ACL。生产环境必须配置认证和最小权限。

建议:

  • 不暴露公网。
  • 启用密码或 ACL。
  • 不用默认用户承担所有业务。
  • 不同业务使用不同账号。
  • 禁用或限制高危命令。

23.2 网络安全 ​

  • 只允许内网访问。
  • 使用安全组、防火墙。
  • 需要时启用 TLS。
  • 运维端口和业务端口隔离。

23.3 命令风险 ​

谨慎使用:

  • FLUSHALL
  • FLUSHDB
  • CONFIG
  • KEYS
  • MONITOR
  • DEBUG

生产中应限制权限并保留审计。

24. 分布式锁 ​

24.1 单实例正确释放 ​

加锁:

bash
SET lock:resource unique-token NX PX 30000

释放:

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

关键点:

  • value 必须唯一。
  • 必须设置过期时间。
  • 释放时必须校验 value。
  • 锁的有效期要覆盖业务执行时间。
  • 不能用简单 DEL 释放锁。

24.2 Redlock ​

Redlock 使用多个独立 Redis 主节点,客户端需要在多数节点上获得锁,并且获取锁耗时小于锁有效期。

适合需要比单实例更强容错的分布式锁场景。但要理解:

  • 它依赖时钟漂移假设。
  • 长时间任务需要 fencing token 或其他保护。
  • 网络分区会带来可用性代价。
  • 对强一致要求极高的场景,应评估 ZooKeeper、etcd、数据库乐观锁等方案。

24.3 工程建议 ​

  • 能不用分布式锁就不用。
  • 能用数据库唯一约束或乐观锁解决就优先用数据库。
  • 锁只保护短小临界区。
  • 锁失败要有重试退避。
  • 持锁业务必须幂等。
  • 监控锁等待、失败率、超时释放。

25. Java / Spring 使用建议 ​

25.1 客户端选择 ​

Java 常见客户端:

  • Lettuce:异步、响应式支持好,Spring Data Redis 默认常用。
  • Jedis:API 简洁,传统使用广泛。
  • Redisson:提供分布式对象、锁、限流器等高级抽象。

选择时关注:

  • 是否支持 Cluster/Sentinel。
  • 连接池或连接复用模型。
  • 超时和重试配置。
  • 序列化方式。
  • 监控指标。

25.2 序列化 ​

常见序列化:

  • String。
  • JSON。
  • JDK 序列化。
  • Protobuf、Kryo 等二进制格式。

建议:

  • Key 使用可读字符串。
  • Value 选择稳定、跨语言、可演进的格式。
  • 不建议用 JDK 序列化做长期缓存格式。
  • JSON 字段变更要兼容旧数据。

25.3 超时配置 ​

必须配置:

  • 连接超时。
  • 命令超时。
  • 连接池最大连接数。
  • 最大等待时间。
  • 重试次数和退避。

不要让 Redis 客户端在故障时无限等待,否则会拖垮业务线程池。

26. 监控与运维 ​

26.1 核心指标 ​

指标含义
used_memory已用内存
mem_fragmentation_ratio内存碎片比
connected_clients客户端连接数
blocked_clients阻塞客户端
instantaneous_ops_per_sec每秒操作数
keyspace_hits/misses缓存命中和未命中
evicted_keys被淘汰 key 数
expired_keys过期 key 数
rejected_connections被拒连接
master_repl_offset复制偏移量

26.2 常用排查命令 ​

bash
INFO
INFO memory
INFO stats
CLIENT LIST
SLOWLOG GET 20
LATENCY DOCTOR
MEMORY USAGE key
DBSIZE

26.3 备份 ​

建议:

  • 定期备份 RDB/AOF。
  • 备份文件上传到独立存储。
  • 定期做恢复演练。
  • 备份和复制不是一回事,副本也会复制误删除。

27. 常见事故与排查 ​

27.1 Redis CPU 飙高 ​

可能原因:

  • 大 key 操作。
  • 慢 Lua 脚本。
  • KEYS 扫描。
  • 大集合交并差。
  • 热 key。
  • 连接数异常。

处理:

  • 查 SLOWLOG。
  • 看 INFO commandstats。
  • 抓客户端调用日志。
  • 暂停高风险任务。
  • 拆分或删除大 key。

27.2 内存持续上涨 ​

可能原因:

  • key 没有 TTL。
  • TTL 太长。
  • 大 key 增长。
  • 缓存穿透导致大量空 key。
  • AOF/复制 buffer 占内存。

处理:

  • INFO memory。
  • redis-cli --bigkeys。
  • 抽样分析 key 前缀。
  • 设置 maxmemory 和淘汰策略。
  • 修复业务 TTL。

27.3 缓存命中率低 ​

可能原因:

  • TTL 太短。
  • key 设计不稳定。
  • 淘汰策略不适合。
  • 内存不够频繁淘汰。
  • 预热不足。
  • 查询参数过度离散。

处理:

  • 看 keyspace_hits 和 keyspace_misses。
  • 看 evicted_keys 和 expired_keys。
  • 分析 key 前缀维度。
  • 调整 TTL、容量、策略。

28. 设计案例 ​

28.1 登录验证码 ​

text
Key: login:code:{phone}
Value: code
TTL: 5 分钟

命令:

bash
SET login:code:13800000000 938201 EX 300
GET login:code:13800000000
DEL login:code:13800000000

注意:

  • 校验成功后删除。
  • 限制发送频率。
  • 不在日志中明文打印验证码。

28.2 用户资料缓存 ​

text
Key: user:profile:{userId}
Value: JSON 或 Hash
TTL: 30 分钟 + 随机抖动

更新:

  1. 更新数据库。
  2. 删除缓存。
  3. 下次读取重建。

28.3 排行榜 ​

text
Key: rank:article:daily:2026-06-25
Type: Sorted Set
bash
ZINCRBY rank:article:daily:2026-06-25 1 article:1001
ZREVRANGE rank:article:daily:2026-06-25 0 9 WITHSCORES
EXPIRE rank:article:daily:2026-06-25 604800

28.4 接口限流 ​

text
Key: rate:{userId}:{api}:{window}
Value: count
TTL: window seconds

Lua 保证 INCR 和首次 EXPIRE 的原子性。

29. 面试与项目表达 ​

29.1 Redis 为什么快 ​

可以回答:Redis 主要操作内存数据结构,命令执行路径短,协议简单,并通过 I/O 多路复用处理大量连接。性能还与命令复杂度、网络往返、数据大小、持久化和复制配置有关,所以不能只说“单线程所以快”。

29.2 Redis 持久化怎么选 ​

RDB 是快照,适合备份和快速恢复,但可能丢失最近一段数据。AOF 记录写命令,数据丢失窗口更小,但文件更大且需要 rewrite。生产中常根据数据价值、恢复时间和性能要求选择 RDB、AOF 或两者结合。纯缓存可以不持久化,但要有预热和降级方案。

29.3 Redis 和数据库一致性怎么做 ​

多数缓存使用最终一致。常见做法是先更新数据库,再删除缓存,并给缓存设置 TTL。高并发下可配合消息队列、binlog 订阅、延迟双删或强制读主库。核心交易数据不要以缓存作为唯一事实来源。

29.4 Redis Cluster 怎么分片 ​

Cluster 将 keyspace 划分为 16384 个 hash slot,每个主节点负责一部分 slot。客户端根据 key 计算 slot 并访问对应节点,遇到迁移会处理 MOVED/ASK。多 key 操作必须在同一 slot,可以用 hash tag 控制同槽。

30. 实战练习 ​

30.1 基础练习 ​

  1. 使用 String 保存 token,并设置 TTL。
  2. 使用 Hash 保存用户资料。
  3. 使用 Set 实现点赞去重。
  4. 使用 Sorted Set 实现排行榜。
  5. 使用 SCAN 查找指定前缀 key。

30.2 进阶练习 ​

  1. 实现 Cache Aside 用户缓存。
  2. 模拟缓存穿透并用空值缓存解决。
  3. 使用 Lua 实现滑动窗口或固定窗口限流。
  4. 使用 Pipeline 批量读取 1000 个 key。
  5. 使用 Stream 和消费者组处理订单事件。

30.3 高级练习 ​

  1. 配置 RDB、AOF,并测试故障恢复。
  2. 搭建主从复制并观察复制延迟。
  3. 搭建 Sentinel,演练主节点故障转移。
  4. 搭建 Cluster,测试 hash tag 和跨 slot 限制。
  5. 制造大 Key,观察 SLOWLOG、MEMORY USAGE、UNLINK 效果。

31. 常见坑清单 ​

  1. 缓存 key 没有 TTL。
  2. 在线上使用 KEYS *。
  3. 存储超大 value 或超大集合。
  4. 使用 HGETALL 获取大 Hash。
  5. 把 Redis 当关系型数据库做复杂查询。
  6. 分布式锁释放时没有校验 token。
  7. 忽略 Redis 复制是异步的。
  8. 以为 Pipeline 等于事务。
  9. 使用 Pub/Sub 做可靠消息。
  10. Cluster 下多 key 跨 slot。
  11. 没有配置客户端超时。
  12. AOF rewrite 或 RDB fork 时没有预留内存。
  13. 把副本当强一致读库。
  14. 监控只看 QPS,不看命中率、淘汰、延迟。
  15. 没有备份恢复演练。

32. 官方资料 ​

基于 VitePress 构建的个人学习文档知识库