Redis 从单机到集群:持久化、主从复制、哨兵与集群完全指南

上一篇文章我们聊了 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

  1. 随机抽查:从设置了过期时间的 Key 中随机抽一批(默认 20 个)。
  2. 删除过期:把其中过期的删掉。
  3. 继续或停止:如果这批里过期比例超过 25%,就继续再抽一批;低于 25% 就收工。
  4. 时间限制:每次最多占用 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 文件,数据量大
增量同步(部分重同步) 从节点断线重连 只传输断开期间丢失的命令,效率高

第一次连接时,只能用全量同步。后续断线重连时,优先尝试增量同步。

全量同步:第一次怎么把数据全搬过来?

全量同步是从节点第一次连接主节点时执行的操作。整个过程分为四个步骤:

详细拆解

  1. 从节点发起请求 :从节点向主节点发送 psync ? -1------? 表示不知道主节点的运行 ID,-1 表示没有偏移量。
  2. 主节点返回确认 :主节点回复 +FULLRESYNC {runid} {offset},告诉从节点自己的运行 ID 和初始偏移量。
  3. 主节点生成 RDB :主节点执行 bgsave 在后台生成 RDB 快照文件。同时用复制缓冲区(replication buffer) 记录从此刻开始的所有写命令。
  4. 发送 RDB 文件 :主节点将 RDB 文件发送给从节点。从节点收到后清空自己的旧数据,然后加载 RDB 文件,把状态恢复到主节点生成 RDB 时的样子。
  5. 发送缓冲区的写命令:主节点把复制缓冲区里记录的写命令全部发送给从节点。从节点执行这些命令,最终追上主节点的最新状态。

注意 :全量同步期间,主节点非阻塞,可以继续处理客户端的读写请求。从节点在加载 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,核心优化有两个:

  1. 主从切换场景 :当一个从节点被提升为新主后,其他从节点可以通过 psync2 直接对新主做部分重同步,无需全量同步。
  2. 从节点重启场景 :从节点重启后,也可以通过 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 哨兵从所有从节点中筛选出一个最合适的当新主。筛选规则如下:

  1. 剔除不合格的 :与主节点断开时间超过 down-after-milliseconds * 10 的从节点直接淘汰。
  2. 看优先级slave-priority 值越小优先级越高,设置为 0 则永不参与选举。
  3. 看数据新旧slave-priority 相同的情况下,复制偏移量(replication offset) 越大的从节点数据越新,优先级越高。
  4. 看运行 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 的"高可用"和"高可靠"就不再是零散的知识点,而是一整套环环相扣的系统设计。

相关推荐
老孙讲技术1 小时前
【监控开发】把车间未戴帽和烟火报警接进安监值班群:workwearDetect 对图与 setMessageCallback 订 smokeAlarm
后端·物联网·音视频开发
计算机毕设定制辅导-无忧学长1 小时前
基于Spring Boot的玄幻小说个性化推荐平台设计与实现
java·vue.js·spring boot·后端·mysql·推荐算法
码路漫漫1 小时前
LRU 真的能解决日志按业务 Key 分文件的落地瓶颈吗?
java·后端
小满zs1 小时前
Go语言第十一章(协程)
后端·go
星星落进兜里2 小时前
Redis 内存缓存,面试补充
数据库·redis·面试
小程故事多_802 小时前
企业AI对话记忆架构实战,告别单一Redis存储,搭建长短效协同的记忆体系
人工智能·redis·架构
1001101_QIA3 小时前
从 CPU 到 GPU:高速缓存、指令并行与 CUDA 执行原理入门
java·后端·spring
2501_928996223 小时前
信创备份一体机性能焦虑根源与中科热备国产CPU平台实测拆解
后端·数据安全·测试
程序员清风3 小时前
聊聊怎么缓解找工作的焦虑感?
java·后端·面试