外观
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 100DEL 同步删除,删除大 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 NX3.2 计数器
bash
INCR article:1001:views
INCRBY article:1001:views 10
DECR stock:sku1001INCR 是原子操作,适合访问量、库存预扣、限流计数。但库存最终一致性还需要数据库校验和补偿。
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
end3.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 age4.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 55.2 使用场景
- 简单队列。
- 最新列表。
- 时间线片段。
- 固定长度日志。
5.3 固定长度列表
bash
LPUSH recent:articles article:1001
LTRIM recent:articles 0 995.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:likes6.2 集合运算
bash
SINTER tag:java tag:redis
SUNION tag:java tag:database
SDIFF followers:alice followers:bob6.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:17.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-09.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
最常见模式:
- 读缓存。
- 命中则返回。
- 未命中则读数据库。
- 写入缓存并设置 TTL。
- 返回数据。
伪代码:
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
EXECMULTI 后命令进入队列,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 current15.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:1001Redis 根据 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-lrumaxmemory 限制 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 更短的 key | TTL 能表达价值 |
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 命令都有时间复杂度。不要只记命令名,要查命令复杂度。
高风险命令:
KEYSSMEMBERS大集合HGETALL大 HashLRANGE超大范围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慢查询只统计命令执行时间,不包含网络传输和排队时间。还应结合:
INFOLATENCY DOCTOR- 客户端耗时日志
- 系统 CPU、内存、磁盘、网络
23. 安全
23.1 认证与 ACL
Redis 支持用户和 ACL。生产环境必须配置认证和最小权限。
建议:
- 不暴露公网。
- 启用密码或 ACL。
- 不用默认用户承担所有业务。
- 不同业务使用不同账号。
- 禁用或限制高危命令。
23.2 网络安全
- 只允许内网访问。
- 使用安全组、防火墙。
- 需要时启用 TLS。
- 运维端口和业务端口隔离。
23.3 命令风险
谨慎使用:
FLUSHALLFLUSHDBCONFIGKEYSMONITORDEBUG
生产中应限制权限并保留审计。
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
DBSIZE26.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 分钟 + 随机抖动更新:
- 更新数据库。
- 删除缓存。
- 下次读取重建。
28.3 排行榜
text
Key: rank:article:daily:2026-06-25
Type: Sorted Setbash
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 60480028.4 接口限流
text
Key: rate:{userId}:{api}:{window}
Value: count
TTL: window secondsLua 保证 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 基础练习
- 使用 String 保存 token,并设置 TTL。
- 使用 Hash 保存用户资料。
- 使用 Set 实现点赞去重。
- 使用 Sorted Set 实现排行榜。
- 使用
SCAN查找指定前缀 key。
30.2 进阶练习
- 实现 Cache Aside 用户缓存。
- 模拟缓存穿透并用空值缓存解决。
- 使用 Lua 实现滑动窗口或固定窗口限流。
- 使用 Pipeline 批量读取 1000 个 key。
- 使用 Stream 和消费者组处理订单事件。
30.3 高级练习
- 配置 RDB、AOF,并测试故障恢复。
- 搭建主从复制并观察复制延迟。
- 搭建 Sentinel,演练主节点故障转移。
- 搭建 Cluster,测试 hash tag 和跨 slot 限制。
- 制造大 Key,观察
SLOWLOG、MEMORY USAGE、UNLINK效果。
31. 常见坑清单
- 缓存 key 没有 TTL。
- 在线上使用
KEYS *。 - 存储超大 value 或超大集合。
- 使用
HGETALL获取大 Hash。 - 把 Redis 当关系型数据库做复杂查询。
- 分布式锁释放时没有校验 token。
- 忽略 Redis 复制是异步的。
- 以为 Pipeline 等于事务。
- 使用 Pub/Sub 做可靠消息。
- Cluster 下多 key 跨 slot。
- 没有配置客户端超时。
- AOF rewrite 或 RDB fork 时没有预留内存。
- 把副本当强一致读库。
- 监控只看 QPS,不看命中率、淘汰、延迟。
- 没有备份恢复演练。
32. 官方资料
- Redis data types:https://redis.io/docs/latest/develop/data-types/
- Redis commands:https://redis.io/docs/latest/commands/
- Redis transactions:https://redis.io/docs/latest/develop/using-commands/transactions/
- Redis pipelining:https://redis.io/docs/latest/develop/using-commands/pipelining/
- Redis Lua scripting:https://redis.io/docs/latest/develop/programmability/eval-intro/
- Redis functions:https://redis.io/docs/latest/develop/programmability/functions-intro/
- Redis persistence:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
- Redis replication:https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
- Redis Sentinel:https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/
- Redis Cluster specification:https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/
- Redis key eviction:https://redis.io/docs/latest/develop/reference/eviction/
- Redis distributed locks:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/