主从复制、哨兵和 Cluster 有什么区别?
一句话回答
三者解决的问题层层递进。主从复制把主节点的数据异步复制到从节点,提供数据冗余和读扩展,但主节点挂了要手动切换;哨兵在主从的基础上监控节点,主节点故障时自动选出新的主节点并通知客户端,解决了高可用,但写入和容量仍受限于一个主节点;Cluster 把数据按 16384 个哈希槽分片到多个主节点,每个主节点再带从节点,同时解决了容量、写扩展和高可用,客户端访问到错误的节点时会收到 MOVED 重定向。
详细解析
主从复制
从节点执行 REPLICAOF <主节点 IP> <端口> 后开始同步:
- 全量同步:第一次连接时,主节点执行 BGSAVE 生成 RDB 发给从节点,同时把这期间的新写命令缓存起来;从节点清空旧数据、加载 RDB,再执行缓存的命令
- 命令传播:之后主节点把每条写命令异步发给从节点,从节点定期汇报自己的复制偏移量(offset)
- 断线后的增量复制:从节点带着主节点的复制 ID 和自己的 offset 请求续传(
PSYNC)。主节点有一个环形的复制积压缓冲区(repl-backlog-size,默认 1MB),缺失的部分还在缓冲区里,就只补发这一部分;否则重新全量同步
从节点默认只读,可以分担读流量,也可以在从节点上做持久化和备份,减轻主节点的压力。RDB 的原理见 RDB 和 AOF。复制是异步的,从节点可能读到旧数据;主节点宕机后,需要人工把某个从节点提升为主节点(REPLICAOF NO ONE),再让其他从节点和客户端改连新的主节点。
哨兵(Sentinel)
哨兵是独立运行的进程,一般部署 3 个及以上的奇数个:
- 监控:每个哨兵定期 PING 主从节点,超过
down-after-milliseconds没有有效回复,就认为它主观下线 - 确认:认为主节点下线的哨兵数达到配置的 quorum,主节点被判定为客观下线
- 选举:哨兵之间选出一个领导者来执行故障转移,需要过半哨兵投票同意
- 故障转移:领导者按优先级、复制偏移量(数据越新越好)、运行 ID 从从节点里选出新的主节点并提升它,再让其他从节点复制新主节点
- 通知:客户端先向哨兵询问当前主节点的地址,再直接连接主节点读写;切换后哨兵会发布新主节点的地址,客户端据此重连
哨兵解决了自动故障转移,但仍然只有一个主节点负责写入,数据量也受限于单机内存。
Cluster
客户端(缓存了"槽 → 节点"的映射)
/ | \
主 A:0 ~ 5460 主 B:5461 ~ 10922 主 C:10923 ~ 16383
| | |
从 A1 从 B1 从 C1
- 分片:
slot = CRC16(key) % 16384,每个主节点负责一部分槽,扩容缩容就是在节点之间迁移槽 - 高可用:每个主节点带从节点,主节点故障时由其他主节点投票,把它的从节点提升上来,不需要哨兵
- 重定向:命令发到了不负责这个槽的节点,会收到
-MOVED 3999 10.0.0.2:6379,客户端更新本地的映射后重新发送;槽正在迁移时会收到-ASK,只对这一次请求转向目标节点,不更新映射 - 多 key 操作:MGET、事务、Lua 脚本涉及的 key 必须在同一个槽里,否则报 CROSSSLOT 错误。可以用哈希标签
{user:1001}:profile、{user:1001}:orders,只有花括号里的部分参与计算,保证它们落在同一个槽 - Cluster 只支持 0 号数据库
对比和选择
| 主从复制 | 哨兵 | Cluster | |
|---|---|---|---|
| 解决的问题 | 数据冗余、读扩展 | 自动故障转移 | 数据分片、写扩展、自动故障转移 |
| 写入节点 | 1 个主节点 | 1 个主节点 | 多个主节点 |
| 容量上限 | 单机内存 | 单机内存 | 所有主节点内存之和 |
| 故障转移 | 手动 | 哨兵自动完成 | 节点之间投票自动完成 |
| 客户端 | 直接连主从节点 | 先向哨兵查询主节点地址 | 需要支持槽路由和重定向 |
| 多 key 操作 | 不受限制 | 不受限制 | 只能在同一个槽内 |
- 数据量小、能接受人工切换:一主一从或一主多从
- 数据能放进单机内存,需要自动故障转移:哨兵,比如一主两从加三个哨兵
- 数据量或写入量超出单机的能力:Cluster
代码示例
用 ioredis 连接哨兵和 Cluster:
import Redis from 'ioredis'
// 哨兵:传入哨兵地址和主节点名称,ioredis 会找到当前的主节点,切换后自动重连
const redis = new Redis({
sentinels: [
{ host: '10.0.0.11', port: 26379 },
{ host: '10.0.0.12', port: 26379 },
{ host: '10.0.0.13', port: 26379 },
],
name: 'mymaster',
})
// Cluster:只需提供部分节点,ioredis 会拉取槽的分布,并自动处理 MOVED 和 ASK
const cluster = new Redis.Cluster([
{ host: '10.0.0.21', port: 6379 },
{ host: '10.0.0.22', port: 6379 },
])
面试官可能追问
主从切换会丢数据吗?什么是脑裂?
会。复制是异步的,主节点已经确认的写入,可能还没同步到从节点就宕机了。脑裂是指主节点和哨兵之间的网络断开了,但主节点本身还活着:哨兵选出了新的主节点,旧主节点仍在接收部分客户端的写入;网络恢复后,旧主节点变成从节点,清空数据去同步新主节点,这期间写入的数据就丢了。可以配置 min-replicas-to-write 1 和 min-replicas-max-lag 10,主节点发现能正常复制的从节点不够时拒绝写入,缩小丢失的窗口。
Cluster 为什么是 16384 个槽?
节点之间通过 Gossip 协议交换心跳,每条心跳消息都带着发送者负责哪些槽的位图。16384 个槽的位图是 2KB,如果用 65536 个槽就是 8KB,心跳的网络开销太大;而 Redis 作者建议的集群规模不超过 1000 个主节点,16384 个槽已经足够分配。
全量同步代价很大,怎么避免频繁发生?
全量同步要 fork 生成 RDB、占用网络带宽传输,从节点加载 RDB 期间通常也无法正常提供服务。避免频繁全量同步:调大 repl-backlog-size,让短暂断线后能增量续传;控制单个实例的内存大小;从节点很多时可以级联复制(从节点下面再挂从节点),减轻主节点的压力。
易错点
- 哨兵只负责监控和故障转移,不存数据,也不做分片
- Cluster 自带故障转移,不需要再部署哨兵
- MOVED 会让客户端更新槽的映射,ASK 只是迁移期间的一次性转向
- 主从复制是异步的,刚写完就从从节点读,可能读不到
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。