上一篇文章我们聊了 Redis 的底层数据结构,这次我们把视角拉高------单机运行得好好的,数据怎么不丢?内存满了怎么办?从节点挂了怎么追?主节点挂了怎么自动恢复?单机装不下了又该怎么办?
这篇文章按一条主线层层递进,回答这五个问题。
主线:从"数据不丢"到"服务不挂"

第一站:日志(持久化)------数据丢了怎么办?
Redis 把数据存在内存里,重启就全没了。为了解决这个问题,Redis 提供了两种"日志"机制:RDB 快照 和AOF 日志。
RDB:定期拍照
RDB 就像给数据拍了一张全家福 ------把当前内存里的所有数据压缩成一个二进制文件(dump.rdb)存到磁盘上。
| 维度 | 说明 |
|---|---|
| 优点 | 文件小、恢复快,适合做备份 |
| 缺点 | 拍照有间隔,两次拍照之间的数据全部丢失 |
| 触发方式 | save 900 1(900秒内至少1次修改就拍)、手动 BGSAVE |
AOF:逐条记账
AOF 正好相反------它记录每一条写命令 ,像一个账本。服务器重启后,把账本里的命令逐条重放一遍,数据就回来了。
| 维度 | 说明 |
|---|---|
| 优点 | 数据更安全,丢失风险低 |
| 缺点 | 文件大、恢复慢 |
| 刷盘策略 | always(最安全)、everysec(生产首选 )、no(最快) |

AOF 重写:文件太大怎么办?
AOF 文件会越来越大,Redis 会在后台自动重写(BGREWRITEAOF),把多条命令合并成一条 。比如 INCR counter 执行了 100 次,重写后变成一条 SET counter 100,文件瞬间缩小。
重写过程不阻塞主线程,完全在后台进行。
混合模式:我全都要
同时开启 RDB 和 AOF ,Redis 重启时优先加载 AOF(数据更完整)。Redis 4.0 之后支持 AOF + RDB 混合持久化:AOF 重写时直接把 RDB 内容写在文件开头,后面再追加增量命令,兼顾了恢复速度和数据安全。
一句话总结 :RDB 适合备份,AOF 适合防丢,两个一起开最稳妥。
第二站:过期删除------过期的 Key 怎么清理?
给 Key 设置了过期时间(EXPIRE key 60),60 秒后 Redis 怎么把它删掉?
答案是两种策略配合使用 :惰性删除 + 定期删除。

惰性删除:用的时候才检查
当你去 GET 一个 Key 时,Redis 先检查它过期了没有。过期了就直接删掉,返回空;没过期就正常返回。
- 优点:不浪费 CPU,平时不检查。
- 缺点 :如果某个 Key 过期后一直没人访问,它就永远占着内存------这就是"僵尸键"。
定期删除:主动出击清扫僵尸
为了解决"僵尸键"问题,Redis 会定期主动扫描 。它每秒钟执行 10 次(由 hz 参数控制)后台任务 activeExpireCycle:
- 随机抽查:从设置了过期时间的 Key 中随机抽一批(默认 20 个)。
- 删除过期:把其中过期的删掉。
- 继续或停止:如果这批里过期比例超过 25%,就继续再抽一批;低于 25% 就收工。
- 时间限制:每次最多占用 CPU 25 毫秒,绝不阻塞主线程。
大量 Key 同时过期怎么办?
如果一万个 Key 都设成 60 秒后过期,到了第 60 秒,Redis 会忙得不可开交。解决方案 :给过期时间加一点随机偏移,比如 EXPIRE key 60 + random(0,10),把压力打散。
第三站:淘汰策略------内存满了怎么办?

过期删除是"定时清理垃圾",但如果写入速度超过清理速度 ,内存还是会满。这时候就需要淘汰策略------内存满了,删谁?
Redis 提供了 8 种淘汰策略,按"淘汰范围"和"淘汰算法"两个维度划分:
| 策略 | 淘汰范围 | 淘汰算法 | 适用场景 |
|---|---|---|---|
| noeviction | 不淘汰 | 写入报错 | 绝不允许丢数据的场景 |
| allkeys-lru | 所有 Key | 最近最少使用 | 通用缓存首选 |
| allkeys-lfu | 所有 Key | 最不经常使用 | 有明显长期热点的场景 |
| allkeys-random | 所有 Key | 随机淘汰 | 数据访问均匀的场景 |
| volatile-lru | 有过期时间的 Key | 最近最少使用 | 既想缓存又想保留核心数据 |
| volatile-lfu | 有过期时间的 Key | 最不经常使用 | 同上,但更关注访问频率 |
| volatile-random | 有过期时间的 Key | 随机淘汰 | 同上 |
| volatile-ttl | 有过期时间的 Key | 淘汰剩余时间最短的 | 优先淘汰即将过期的 |
LRU vs LFU
- LRU(Least Recently Used):看"最近一次访问时间",淘汰最久没被访问的。
- LFU(Least Frequently Used):看"累计访问频率",淘汰访问次数最少的。
举个例子 :一个 Key 昨天被狂刷 10000 次,今天没人碰------LRU 会觉得它"不常用"而淘汰它,LFU 则会保留它。有长期热点的场景选 LFU,一般缓存选 LRU。
配置方式 :
maxmemory 2gb设置内存上限,maxmemory-policy allkeys-lru选择策略。maxmemory-samples 5控制采样数量------越大淘汰越精准,但 CPU 开销也越高。
第四站:事务------怎么保证一组命令要么全做要么全不做?
Redis 的事务和 MySQL 不太一样。它通过 MULTI、EXEC、DISCARD、WATCH 四个命令来实现。
基本用法
text
> MULTI # 开启事务
OK
> SET user:1 name "张三"
QUEUED # 命令入队,不执行
> INCR user:1:age
QUEUED
> EXEC # 一次性执行所有命令
1) OK
2) (integer) 25
MULTI 之后所有命令只入队不执行,直到 EXEC 才一次性按顺序执行 。执行期间,其他客户端的请求插不进来。

WATCH:乐观锁
如果事务依赖某个值不被修改(比如扣库存),可以用 WATCH 监视 Key:
text
> WATCH stock:iphone # 盯着库存
OK
> MULTI
OK
> DECR stock:iphone # 扣库存
QUEUED
> EXEC # 如果期间 stock:iphone 被改了,返回 nil,事务取消
(nil)

WATCH 实现了 CAS(Check-And-Set) 语义------乐观锁,不阻塞其他客户端。
和 MySQL 事务的区别
Redis 的事务不支持回滚 ------如果执行中某条命令出错(比如对 String 执行 List 操作),其他命令照常执行。但 WATCH 提供了一种"取消"机制:被监控的 Key 发生变化时,EXEC 会直接放弃整个事务。
| 区别 | Redis 事务 | MySQL 事务 |
|---|---|---|
| 回滚 | ❌ 不支持,但 WATCH 可整体放弃事务 | ✅ 支持回滚 |
| 隔离性 | 弱隔离,只是"串行执行" | 快照隔离 / 可重复读 |
| 原子性 | 部分原子(不支持回滚) | 完整原子 |
| 适用场景 | 原子性要求不高、冲突少的场景 | 要求严格的 ACID 场景 |
第五站:主从复制------数据怎么备份?从节点挂了怎么追?
什么是主从复制?
主从复制就是把一台 Redis 服务器的数据,复制到其他 Redis 服务器上 。前者叫主节点(Master) ,后者叫从节点(Slave/Replica) 。
主从复制解决了三个问题:
- 数据备份:从节点存了一份副本,主节点数据丢了还能恢复。
- 高可用:配合哨兵,主节点挂了从节点能顶上。
- 读写分离:主节点负责写,从节点负责读,分担压力。

两种同步方式
Redis 的主从同步分为两种:
| 同步方式 | 触发时机 | 特点 |
|---|---|---|
| 全量同步(完整重同步) | 从节点第一次连接主节点 | 传输整个 RDB 文件,数据量大 |
| 增量同步(部分重同步) | 从节点断线重连 | 只传输断开期间丢失的命令,效率高 |
第一次连接时,只能用全量同步。后续断线重连时,优先尝试增量同步。
全量同步:第一次怎么把数据全搬过来?
全量同步是从节点第一次连接主节点时执行的操作。整个过程分为四个步骤:

详细拆解:
- 从节点发起请求 :从节点向主节点发送
psync ? -1------?表示不知道主节点的运行 ID,-1表示没有偏移量。 - 主节点返回确认 :主节点回复
+FULLRESYNC {runid} {offset},告诉从节点自己的运行 ID 和初始偏移量。 - 主节点生成 RDB :主节点执行
bgsave在后台生成 RDB 快照文件。同时用复制缓冲区(replication buffer) 记录从此刻开始的所有写命令。 - 发送 RDB 文件 :主节点将 RDB 文件发送给从节点。从节点收到后清空自己的旧数据,然后加载 RDB 文件,把状态恢复到主节点生成 RDB 时的样子。
- 发送缓冲区的写命令:主节点把复制缓冲区里记录的写命令全部发送给从节点。从节点执行这些命令,最终追上主节点的最新状态。
注意 :全量同步期间,主节点非阻塞,可以继续处理客户端的读写请求。从节点在加载 RDB 期间会短暂阻塞。
增量同步:断线重连后怎么快速追上?
全量同步要传输整个 RDB 文件,数据量大、耗时长。所以除了第一次连接,大部分时候主从之间做的是增量同步。
增量同步依赖三个核心机制:

① 复制偏移量(offset)
主节点和从节点各自维护一个偏移量:
- 主节点的偏移量(master_repl_offset) :每向从节点传播 N 个字节的数据,就加 N。
- 从节点的偏移量(slave_repl_offset) :每从主节点收到 N 个字节的数据,就加 N。
如果主从偏移量相同,说明数据一致;如果不同,说明还有数据没同步。
② 复制积压缓冲区(repl_backlog_buffer)
这是主节点维护的一个固定长度的环形队列 ,默认大小 1MB。主节点把写命令发送给从节点的同时,也会往这个缓冲区里写一份。
因为是环形队列,新的数据会覆盖 旧的数据。所以缓冲区只保存最近一段时间的写命令。
③ 运行 ID(runid)
每个 Redis 节点启动时都会生成一个40 位的随机字符串 作为运行 ID。从节点第一次连接主节点时,会保存主节点的 runid。断线重连时,从节点把这个 runid 发给主节点,主节点用来判断:
- runid 相同:说明还是之前那个主节点,可以尝试增量同步。
- runid 不同:说明主节点变了(比如重启过或换了新主),只能全量同步。
PSYNC 命令:增量还是全量,由它决定

从节点重连时,发送 PSYNC {runid} {offset} 命令给主节点。主节点收到后按以下逻辑判断:
三种回复:
+FULLRESYNC {runid} {offset}:执行全量同步。+CONTINUE:执行增量同步,从节点等着接收缺失的数据。-ERR:主节点版本低于 Redis 2.8,不认识PSYNC命令,降级用老式的SYNC命令。
复制积压缓冲区多大才合适?
缓冲区太小,从节点断线时间稍长,数据就被覆盖了,只能全量同步。缓冲区太大,又浪费内存。
计算公式:缓冲空间 = 主库写入速度 × 操作大小 - 主从间网络传输速度 × 操作大小
举例 :主库每秒写入 2000 个操作,每个 2KB,网络每秒只能传 1000 个,那就有 1000 个操作需要缓冲,至少需要 2MB。再留点余量,生产环境一般建议设为 10MB ~ 100MB,具体根据业务写入量评估。
配置参数:repl-backlog-size 100mb。
psync 的演进:psync2(Redis 4.0)
psync 有一个不足:从节点重启后,runid 变了,只能全量同步。生产环境中从节点重启是常有的事,每次都全量同步代价太大。
Redis 4.0 引入了 psync2,核心优化有两个:
- 主从切换场景 :当一个从节点被提升为新主后,其他从节点可以通过
psync2直接对新主做部分重同步,无需全量同步。 - 从节点重启场景 :从节点重启后,也可以通过
master_replid2记录之前同步过的主节点 ID,尝试进行部分重同步。
第六站(重点):哨兵模式------主节点挂了怎么办?
主从复制解决了"读压力大"和"数据备份"的问题,但有一个致命缺陷:如果主节点挂了,整个集群就只能读、不能写了。人工恢复太慢,业务等不起。
哨兵(Sentinel) 就是来解决这个问题的------它是一个独立的监控进程集群,专门盯着 Redis 主从集群,一旦主节点挂了,自动选一个新主出来,全程不需要人插手。
什么是哨兵?
哨兵是一套独立的分布式监控系统,专门盯着 Redis 主从集群。它的职责可以概括为四件事:
| 职责 | 具体内容 |
|---|---|
| 持续监控 | 每隔一段时间给所有 Redis 节点发 PING,看它们活着没 |
| 故障通知 | 发现节点有问题时,通过发布订阅(Pub/Sub)通知其他哨兵和客户端 |
| 自动故障转移 | 主节点挂了,自动选一个从节点当新主 |
| 配置提供者 | 客户端不硬编码主节点地址,而是问哨兵"当前主是谁" |

注意 :哨兵不处理客户端的读写请求,它只负责"看门"和"指挥"------真正的数据读写还是走 Redis 主从节点。
两种下线状态:主观 vs 客观
哨兵判断一个节点"挂了",分成两步:先主观,后客观。

主观下线(SDOWN,Subjective Down) :
- 一个哨兵给主节点发
PING,在down-after-milliseconds(默认 30 秒)内没收到PONG回复。 - 这个哨兵自己认为主节点挂了,标记为"主观下线"。
客观下线(ODOWN,Objective Down) :
- 多个哨兵(数量达到
quorum阈值)都认为这个主节点主观下线。 - 哨兵们达成共识:主节点确实挂了,标记为"客观下线",触发故障转移。
为什么要分两步?防止单个哨兵误判。比如网络抖动了一下,一个哨兵没收到回复就说主挂了,如果直接切主,会造成不必要的故障转移。
配置示例 :
sentinel monitor mymaster 127.0.0.1 6379 2中的2就是quorum------至少 2 个哨兵同意才能判定客观下线。
故障转移全流程
一旦主节点被判定为客观下线,哨兵集群立即启动故障转移。整个过程分为四步:

第一步:选举 Leader(领头哨兵)
故障转移需要一个"总指挥"------从所有哨兵中选出一个 Leader 来主导整个过程。选举采用的是 Raft 算法的变种:
- 每个确认主节点客观下线的哨兵都可以发起投票。
- 获得超过半数哨兵投票的节点成为 Leader。
- 同一轮选举中,每个哨兵只能投一票。
- 如果多个哨兵同时发起选举导致平局,会递增纪元(epoch) 编号重试。
关键点 :哨兵数量必须是奇数且至少 3 个。如果是 2 个哨兵,各投自己一票,永远选不出 Leader。
第二步:从从节点中选新主

Leader 哨兵从所有从节点中筛选出一个最合适的当新主。筛选规则如下:
- 剔除不合格的 :与主节点断开时间超过
down-after-milliseconds * 10的从节点直接淘汰。 - 看优先级 :
slave-priority值越小优先级越高,设置为 0 则永不参与选举。 - 看数据新旧 :
slave-priority相同的情况下,复制偏移量(replication offset) 越大的从节点数据越新,优先级越高。 - 看运行 ID:如果上面都一样,运行 ID 最小的胜出(最终裁决)。
第三步:执行切换
- Leader 向选中的从节点发送
SLAVEOF NO ONE命令,让它正式成为新主节点。 - Leader 向其他所有从节点发送
SLAVEOF <新主IP> <新主端口>命令,让它们切换到新主节点下面。 - 旧主节点如果之后恢复了,会被自动标记为从节点,挂到新主下面。
第四步:通知客户端
切换完成后,哨兵通过发布订阅机制把新主地址推送给订阅了状态变化的客户端。主流 Redis 客户端(如 Jedis、Lettuce、redis-py)都内置了哨兵支持,能自动感知主节点变化。
脑裂问题:哨兵模式的"阿喀琉斯之踵"
脑裂(Split-Brain) 是指一个 Redis 系统里同时出现了两个主节点。
怎么发生的?
最常见场景:网络分区 。假设哨兵集群和主节点被网络切成了两半------一半哨兵认为主节点挂了,选了一个从节点当新主;但旧主节点其实还活着,只是和那一半哨兵失联了。结果就是两个主节点同时存在 ,客户端向两个主节点分别写入数据,数据彻底分裂,无法合并。
怎么防?
- 合理设置
quorum:通常设为N/2 + 1(N 为哨兵总数),确保客观下线是多数派共识。 - 部署奇数个哨兵(3 或 5 个),防止投票平手。
- 客户端使用哨兵模式连接池(如 Redisson),确保能及时拿到最新主节点地址。
- 配置
min-slaves-to-write:主节点至少同步到指定数量的从节点才接受写操作,降低脑裂期间数据丢失风险。
核心配置参数
text
# 监控主节点,quorum=2 表示至少 2 个哨兵同意才判定客观下线
sentinel monitor mymaster 192.168.1.10 6379 2
# 主观下线判定时间:5 秒无响应就标记为 SDOWN
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时:整个切换过程最多 60 秒
sentinel failover-timeout mymaster 60000
# 故障转移后,每次只让 1 个从节点同步新主,避免瞬间压力过大
sentinel parallel-syncs mymaster 1
# 如果 Redis 设置了密码,哨兵也要配
sentinel auth-pass mymaster your_password
部署建议
- 生产环境至少部署 3 个哨兵节点 ,且必须是奇数(3 个哨兵时
quorum设为 2)。 - 哨兵节点不要和 Redis 主从部署在同一台机器上(否则机器挂了,Redis 和哨兵一起消失)。
- 监控项加上:哨兵进程存活、主从切换次数、切换耗时。
第七站:集群模式------单机装不下了怎么办?
哨兵解决了"高可用",但单机内存有上限 (比如 64GB),数据再大就装不下了。Redis Cluster(集群) 就是来解决这个问题的。
哈希槽:数据怎么分?
Redis Cluster 把整个数据空间分成 16384 个哈希槽(Hash Slot) 。
每个 Key 属于哪个槽?计算方式:CRC16(key) % 16384。
16384 个槽分配到多个主节点上。比如 3 个主节点:
- 节点 A:槽 0 ~ 5460
- 节点 B:槽 5461 ~ 10922
- 节点 C:槽 10923 ~ 16383
每个主节点可以配若干个从节点(备份)。

节点间怎么通信?
Redis Cluster 没有中心节点 ,所有节点通过 Gossip 协议 互相通信,交换"谁活着、谁挂了、槽位怎么分配的"等信息。每个节点都存着完整的集群拓扑,不存在单点瓶颈。
客户端怎么找到数据?
客户端算出一个 Key 属于哪个槽,然后问对应的节点。如果客户端的"槽位表"是旧的(比如集群刚扩缩容过),节点会返回 MOVED 错误 ,告诉客户端"这个槽现在归那个节点管了,你去问他"。聪明的客户端会缓存最新的槽位表,减少重定向次数。
故障转移
和哨兵类似------某个主节点挂了,它下面的从节点被选为新主,继续服务。
哨兵 vs 集群:怎么选?
| 对比维度 | 哨兵模式 | 集群模式 |
|---|---|---|
| 核心目标 | 高可用(自动故障转移) | 高可用 + 水平扩展(分片) |
| 数据量 | 单机内存能装下(通常 ≤ 64GB) | 单机装不下,需要分片 |
| 水平扩展 | ❌ 不能(只能加从节点读) | ✅ 可以动态加节点 |
| 复杂度 | 低,3 个哨兵进程即可 | 高,需处理哈希槽、跨槽操作 |
| 跨 Key 操作 | 完全支持 | 有限制(不同槽的 Key 不能放在同一事务) |
| 适用场景 | 中小规模、追求简单 | 大流量、海量数据 |
选型建议 :数据量在单机范围内、追求运维简单 → 哨兵模式 。数据量持续增长、需要横向扩展 → 集群模式。
写在最后:一条主线串起来

回头看这七个模块,其实是一条完整的逻辑链:
| 问题 | 解决方案 | 一句话 |
|---|---|---|
| 数据丢了怎么办? | RDB + AOF 持久化 | 拍照 + 记账,两个一起开 |
| 过期的 Key 怎么清理? | 惰性删除 + 定期删除 | 用的时候查 + 主动定时扫 |
| 内存满了怎么办? | 8 种淘汰策略 | 选最适合业务的策略删 |
| 一组命令怎么原子执行? | MULTI + EXEC + WATCH | 入队后统一执行,WATCH 做乐观锁 |
| 数据怎么备份?从节点挂了怎么追? | 主从复制 | 全量 RDB + 增量命令流,PSYNC 续传 |
| 主节点挂了怎么办? | 哨兵模式 | 3 个哨兵监控,自动切主 |
| 单机装不下了怎么办? | 集群模式 | 16384 个槽,数据分片存 |
从单机到集群,从数据不丢到服务不挂,Redis 在每个环节都给出了成熟的解决方案。 理解了这条主线,Redis 的"高可用"和"高可靠"就不再是零散的知识点,而是一整套环环相扣的系统设计。