Theory · Redis · Replication & Cluster

主从 / 哨兵 / Cluster

异步复制 + 自动故障转移 + 数据分片 —— 高可用与水平扩展的三级阶梯

主从复制

replid + offset 标识数据版本;断线先试 PSYNC 部分重同步,backlog 撑不住才全量 bgsave

哨兵 Sentinel

sdown → odown(quorum)→ 多数派授权选 leader → 提升从库;本质是 AP,脑裂窗口只能缩小

Cluster

16384 slot(CRC16 mod 16384)+ gossip 协议 + MOVED/ASK 重定向——分片与高可用一体

这份 deck 覆盖 Redis 高可用三件套:主从、哨兵、Cluster。主线是三张图:全量同步时序、哨兵故障转移时序、Cluster 槽映射。所有默认值都对着 8.2 的 redis.conf 核过:backlog 1MB、min-replicas-max-lag 10、cluster-node-timeout 15000。把"为什么 16384 个槽"和"quorum 与 majority 的区别"这两道细节题答出来,就能和背二手博客的候选人拉开差距。

Replication Basics

主从复制:异步复制的三个机制

① 连接正常:命令传播

主库把"改变数据集的事件"以命令流发给从库:客户端写、键过期、内存淘汰(都合成 DEL 传播)等——从库是主库写命令的重放器。

② 断线重连:PSYNC

网络抖动或超时断开后,从库自动重连并尝试部分重同步:只补断线期间错过的那段命令流(第 4 页)。

③ 补不上:全量重同步

backlog 不够或 replid 已换代时退回全量:主库 bgsave 快照 + 传输 + 加载 + 补缓冲(第 3 页)。

重要事实(官方文档口径)说明
异步复制主库不等从库确认就回复客户端——低延迟的前提,也是故障转移可能丢已确认写的根源;WAIT 命令可要求 N 个副本确认,但文档明说它"不能把一组实例变成 CP 系统"
双端基本非阻塞主库同步期间照常服务;从库初始同步可继续用旧数据,唯加载 RDB 那一刻短暂阻塞(4.0 起旧数据删除可放后台线程)
从库默认只读replica-read-only yes(2.6 起默认);可写副本官方明说不推荐——命令在主从上重放结果可能不同
级联复制从库可再挂从库(A→B→C);4.0 起子从库收到的是与主库发出的完全相同的流
主从复制的框架就是三个机制:平时命令传播,断线先试部分重同步,补不上才全量。四条重要事实里最值钱的是第一条:异步复制是性能的前提,也是丢写问题的根源,官方原文说 WAIT 只能降低丢写概率、不能变成 CP 系统。还要记住过期键和内存淘汰都会合成 DEL 命令传播给从库,这是主从过期一致性的基础。

Full Resynchronization

全量同步时序:bgsave → 传输 → 补缓冲

Redis 主从全量同步时序图 从库发送 PSYNC,主库执行 BGSAVE 生成 RDB 并缓冲新写命令,回复 FULLRESYNC 后传输 RDB,从库清空旧数据并加载,主库再把缓冲区命令补发给从库,随后进入命令传播稳定态。 ① PSYNC ? -1(或 replid offset) ③ +FULLRESYNC replid offset ④ 传输 RDB(磁盘 / 无盘) ⑥ 补发缓冲区命令(复制缓冲) 主库 Master 生成并发送写命令流 ② BGSAVE 生成 RDB 同时把新写命令存入复制缓冲区 ⑤(主库视角)继续服务写 新增写命令持续进缓冲区 从库 Replica 重放命令流保持一致 清空旧数据 → 加载 RDB 加载期间阻塞连接(大库可达秒级) ⑦ 稳定态:命令传播 —— 写命令 / 过期 DEL / 淘汰 DEL 持续流向从库 多个从库同时请求全量同步时,主库只执行一次 BGSAVE 服务所有人 replica 复制缓冲超限(256mb/64mb 60s)→ 断开重连 → 再全量 大库 + 慢盘易成"全量同步风暴"——低峰同步 / 无盘复制缓解
全量同步七步对着图讲:从库发 PSYNC,主库起 BGSAVE 生成 RDB,同时把这一期间的新写命令全部缓冲,先回 FULLRESYNC 再传 RDB,从库清空旧数据加载,加载完主库把缓冲区命令补发过去,然后进入命令传播的稳定态。两个生产要点:一是主库对多个并发全量请求只做一次 BGSAVE;二是复制缓冲有 client-output-buffer-limit 保护,硬限制 256MB 软限制 64MB 六十秒,超了就断开,大库慢盘就会陷入全量同步风暴,要用无盘复制和低峰同步来破。

Partial Resync · PSYNC

部分重同步:replid + offset + backlog 三件套

判定坐标:replid + offset

replid(大伪随机串)标记一段数据历史,offset 按复制流字节单调递增——(replid, offset) 唯一确定数据集的一个版本。主从握手后从库继承主库 replid

repl_backlog:环形缓冲

主库在内存里维护固定大小的环形缓冲区(repl-backlog-size),持续滚动写入命令流;offset 落在环里且 replid 匹配 → 回 +CONTINUE 只补增量;环已覆盖/ID 不认识 → +FULLRESYNC

追问答案
backlog 默认多大?够吗?默认 1MB(redis.conf 8.2),生产几乎必调大;repl-backlog-ttl 3600——主库长时间无从库连接后 1 小时释放 backlog
backlog 怎么估算?经验公式:平均主库写速率 × 预期最长断线时间(留余量 ×2)。例:写速率 5MB/s、断线容忍 60s → 至少 300MB
从库重启能部分重同步吗?4.0 起、且是正常重启:RDB 里存了 replid/offset(建议先 SHUTDOWN 落盘);AOF 重启不行(没法部分同步 AOF 重启的从库,官方原文)
故障转移后其他从库要全量吗?不用。每个实例有主副两个 replid:从库晋升主库时把旧 replid 存进 secondary,老从库拿旧 ID 来连也能 +CONTINUE
答题口径:"部分重同步的三个前提:replid 能对上(主/副 ID 都行)、offset 还在 backlog 环形缓冲里、主库还活着。任何一个不满足就退回全量——所以 backlog 容量是断线损失的直接预算。"
部分重同步的坐标是 replid 加 offset:ID 标记数据历史,offset 是逻辑时钟。主库的 backlog 是环形缓冲,offset 还在环里就能 CONTINUE 补增量,环被冲掉就全量。三个高频追问:默认 1MB 生产必调,估算公式是写速率乘最长断线时间再留倍余量;从库正常重启靠 RDB 里存的复制元数据也能部分同步,AOF 重启不行;故障转移后新主靠第二个 replid 让旧从库免全量。双 replid 这个细节答出来非常加分。

Diskless · Cascading · Expiry · Config

复制进阶:无盘 / 级联 / 过期键传播 / 参数速查

无盘复制(diskless)

子进程直接把 RDB经 socket 发给从库,不落盘中转——2.8.18 引入;repl-diskless-sync yes(7.0 起默认 yes),repl-diskless-sync-delay 5 秒等待更多从库一起传,摊薄 fork 成本。

级联复制

A→B→C:中间层从库分担主库的同步压力(多从库读流量也由它分摊)。4.0 起子从库收到与顶层主库相同的流,且从库本地写不会传播(数据一致性靠主库单向流)。

过期键怎么传播

从库不主动删,等主库过期/淘汰时合成 DEL 传播;从库用逻辑时钟对读请求"逻辑隐藏"已过期键。晋升为主库后才开始独立删——主从不依赖时钟同步。

参数(redis.conf 8.2)默认一句话
repl-backlog-size1mb部分重同步的环形缓冲——按写速率×断线时长调
client-output-buffer-limit replica256mb 64mb 60从库输出缓冲:硬 256MB 或 60s 内超 64MB 即断开
repl-timeout60s主从心跳/传输超时阈值
repl-diskless-sync / -delayyes / 5无盘复制开关与凑批延迟(7.0 起默认 yes)
replica-priority100哨兵选新主的优先级,0 = 永不晋升
min-replicas-to-write / min-replicas-max-lag0 / 10安全阀:少于 N 个滞后 ≤M 秒的从库时主库拒写(默认关闭,第 10 页)
这页是进阶四件套加参数表。无盘复制的价值是省一次磁盘读写,7.0 起默认开启,delay 五秒是凑批等更多从库。级联复制注意子从库拿的是主库的流。过期键传播必须答对:从库不删,等主库 DEL 传播,靠逻辑时钟对读隐藏。参数表里四个默认值要能脱口而出:backlog 1MB、副本缓冲 256MB 64MB 60 秒、repl-timeout 60、replica-priority 100。min-replicas 两个参数先混个脸熟,脑裂页细讲。

Sentinel · Overview

哨兵 Sentinel:给主从补上自动故障转移

四大任务(官方口径)

监控:持续 ping 主从是否正常;通知:异常时通过 API/脚本告警;自动故障转移:主挂了 → 提升从库为新主 → 其余从库改挂新主 → 通知客户端新地址;配置提供者:客户端向哨兵问"当前主库是谁"(SENTINEL get-master-addr-by-name)。

部署铁律

至少 3 个哨兵实例,放在"独立失败域"(不同物理机/AZ);哨兵端口 26379;必须给可写配置文件(状态要落盘);少数派分区绝不 failover——哨兵自己也要多数派才能干活。两节点哨兵是经典错误部署。

sentinel.conf 关键项(默认值)说明
sentinel monitor <name> <ip> <port> <quorum>监控主库与判定客观下线的 quorum(只管"检测",不管"授权"——见下页)
down-after-milliseconds(默认 30000)PING 无有效回复持续该时长 → 判 sdown
failover-timeout(默认 180000)故障转移总超时;同一 sentinel 对同一 master 重试要等 2× 该值
parallel-syncs(默认 1)切换后允许同时重新同步的从库数——全 1 可保证切换期间始终有从库可读
注意:从库无需配置——哨兵通过主库 INFO 自动发现从库;哨兵之间通过 __sentinel__:hello 频道(每 2 秒发 hello)互相发现。Docker/NAT 端口映射会破坏自动发现,须 announce-ip/-port。
哨兵的定位一句话:给不带分片的主从补一个自动故障转移系统。四大任务里前三个好背,第四个"配置提供者"是客户端视角的关键:客户端不直连主库,而是问哨兵拿当前主库地址,故障转移后拿到的就是新地址。部署三条铁律:至少三个哨兵且跨失败域、少数派绝不 failover、必须可写配置文件。从库和哨兵都是自动发现的,发现机制全靠 hello 频道和 INFO,这个细节能答出来说明真搭过。

Failure Detection & Leader Election

sdown → odown → leader:两级确认 + 多数派授权

sdown:主观下线(本地)

单个哨兵在 down-after-milliseconds 内没收到对 PING 的有效回复(+PONG / -LOADING / -MASTERDOWN 算有效)→ 打 sdown。sdown 只代表"我觉得它挂了",其他实例也可能 sdown。

odown:客观下线(共识)

哨兵用 SENTINEL is-master-down-by-addr 互相询问,quorum 个哨兵都报 sdown → 升级 odown。只对主库生效(从库/哨兵最多 sdown)。odown 触发故障转移流程,但还不等于能执行。

角色门槛作用
quorum配置值(如 2/5)把 sdown 升级为 odown——只负责"确认主库真的挂了",是检测门槛
majority过半哨兵(5 个取 3)授权某个哨兵当 leader 去执行故障转移——是执行门槛;少数派分区永远拿不到
leader 选举epoch(类比 Raft term)+ 先到先得每个哨兵在同一 epoch 只投一票;拿多数票者当选,获唯一 config epoch,新配置版本号大者胜
最容易被追问的点:"quorum 为什么和 majority 分开?"——quorum 可调小以更快感知故障(更敏感),但执行必须过半(防脑裂):5 哨兵 quorum=2 时,2 个就能确认下线,但仍需 3 个授权才能切换。这就是官方说的"故障转移永远不在少数派分区发生"。
检测链是两级确认加多数派授权。sdown 是主观判断:down-after 毫秒内没收到有效 PING 回复就报,注意 LOADING 和 MASTERDOWN 这两个错误也算有效回复。odown 要 quorum 个哨兵通过 is-master-down-by-addr 互相确认,只对主库生效。最关键的一问是 quorum 和 majority 的区别:quorum 管检测可以调小求敏感,majority 管执行必须过半防脑裂。leader 选举是简化版 Raft:epoch 当 term,一个 epoch 一票,先到先得,过半当选。

Failover Timeline

哨兵故障转移:从 sdown 到 +switch-master

哨兵故障转移完整时序 主库失联后哨兵 S1 判定 sdown,与其他哨兵互询达到 quorum 升级 odown,S1 请求投票获得多数派授权当选 leader,leader 按规则选择从库 R1 执行 REPLICAOF NO ONE,通知其余从库改挂新主,向客户端发布 switch-master,旧主恢复后被降级为从库。 M1 · 旧主 哨兵集群 S1 / S2 / S3(quorum=2) 从库 R1 / R2 · 客户端 C M1 宕机 ① S1:down-after-ms 内无有效 PING → +sdown ② is-master-down-by-addr 互询 ③ ≥quorum 报 sdown → +odown(仅主库) ④ S1 请求投票(epoch+1)· 同 epoch 一票 ⑤ S1 拿到多数票(3/5)→ 当选 leader ⑥ REPLICAOF NO ONE ⑦ R2 改挂 R1(并行度 parallel-syncs) ⑧ +switch-master ⑨ M1 恢复 → 降级为从库 分区期间写入全部丢失 成功判定:REPLICAOF NO ONE 成功 + INFO 观察到 role 变化 新配置经 __sentinel__:hello 广播,epoch 版本号大者胜 客户端两条路:订阅 +switch-master 事件 或定期 SENTINEL get-master-addr-by-name 拉取
故障转移九步对着图走:M1 宕机,S1 先主观下线;然后和 S2 S3 互询,quorum 个确认后升客观下线;接着 S1 请求投票,epoch 加一,同 epoch 一票先到先得,拿多数票当选 leader;leader 从 R1 R2 里按规则选优,对 R1 发 REPLICAOF NO ONE,再让 R2 改挂 R1,parallel-syncs 控制同时重配几个;最后通过 switch-master 事件通知客户端。第九步最常被追问:旧主恢复后不是抢回主位,而是被降级成从库,分区期间的写入就丢了。

Replica Selection & Promotion

选谁当新主:先淘汰,再排序(priority → offset → runid)

第一步:先淘汰不合格者

与主库断开超过 down-after-milliseconds × 10 + 主库进入 sdown 的时长 的从库直接出局——数据太旧,晋升会放大丢失。sdown 状态的从库也不会被选。

第二步:三级排序(依次比较)

replica-priority 小者优先(运维显式指定机房偏好,默认 100);② 优先级相同比复制 offset——数据更全的赢;③ 仍相同比 runid 字典序——不为优,只为结果确定性(不用随机)。

追问答案
priority=0 的从库会怎样?永远不会被晋升(运维用来做"纯读副本/异地灾备从库"),但故障转移后仍会被哨兵重配为从库挂到新主
failover 什么时候算成功?对选中从库 REPLICAOF NO ONE 成功执行,且在它的 INFO 里观察到 role 变为 master;随后新配置(带唯一 config epoch)向全体广播
两个哨兵同时想切换怎么办?投过票的哨兵在 2 × failover-timeout 内不再投同一 master 的其他请求——同一时刻只有一个 leader 能推进
哨兵会误判自己挂了怎么办?TILT 保护模式:时钟异常(两次定时中断间隔为负或 ≥2s)时只监控不行动,30 秒正常后退出
答题模板:"先按断线时长淘汰,再按 priority、offset、runid 三级排序;选优本质是'配置优先 + 数据优先 + 确定性'三原则。priority 是给运维的旋钮,offset 才是数据安全的第一道保险。"
新主选举记成两步:先淘汰后排序。淘汰线是 down-after 的十倍加 sdown 时长,断线太久的从库出局。排序三级:优先级小的先、相同比 offset 数据全的赢、再相同比 runid 字典序,注意 runid 不代表能力,只是为了结果确定。priority 等于零是从库级的"永不晋升"开关,但照样会被重配成新主的从库。TILT 模式是冷门加分点:哨兵靠时钟判断,时钟跳变就进入自我保护只监控不行动。

Split Brain · Data Loss Window

脑裂:为什么哨兵防不住丢数据,只能缩小窗口

事故时序(官方文档原样场景)

① 网络分区把旧主 M1 和客户端 C1 划在少数派一侧;② 多数派哨兵完成 failover,R2 晋升新主;③ C1 不知道,继续往 M1 写,M1 照常接受;④ 分区恢复,M1 被重配为 R2 的从库并清空自身数据——分区期间 C1 写入的数据全部丢失。

缓解:让旧主"自杀式拒写"

min-replicas-to-write 1 + min-replicas-max-lag 10:主库发现没有 1 个从库的 ack 滞后 ≤10 秒,就拒绝写入。旧主被隔离后 10 秒即变为不可用——官方文档推荐配置。默认 0/10 即关闭(redis.conf 8.2)。

仍然存在的丢数据窗口说明
① 拒写前的滞后窗口检测有延迟:拒写生效前的写入(≤min-replicas-max-lag 级别)已接受但未到达从库——切换后照样丢
② 异步复制的固有窗口主库回复 OK 与传播到从库之间有时间差;主库恰好在这瞬间挂掉、未收到写的从库被晋升 → 该写永久丢失
③ WAIT 也不解决官方原文:WAIT 只保证"有 N 份 ack","不能把实例组变成 CP 系统"——已确认的写仍可能在故障转移中丢失
标准答案句式:"Redis 主从+哨兵是 AP 系统:异步复制 + 'last failover wins'。min-replicas-to-write 只是把无限丢失窗口收窄成秒级,不能根除。要强一致只能换共识系统(ZooKeeper/etcd)或业务侧接受最终一致。"
脑裂题的正确答法分三段。先讲场景:旧主在少数派分区继续服务旧客户端,分区恢复后被降级清空,期间写入全丢。再讲缓解:min-replicas-to-write 加 min-replicas-max-lag,让被隔离的旧主十秒内拒写,把丢失窗口从"无限"收窄到"秒级"。最后讲不可根除的两层窗口:拒写生效前的延迟,和异步复制回复与传播之间的时间差,官方原文说 WAIT 也救不了。收尾必须落到 AP 系统的定性上,这是面试官想听的架构判断。

Cluster · Sharding Model

Cluster:数据分片 + 内置高可用

Redis Cluster 槽映射:key 经 CRC16 mod 16384 定位到节点 key 先经 hash tag 提取再计算 CRC16 模 16384 得到槽号,16384 个槽被划分给三个主节点,每个主节点带一个从节点;槽范围 0-5460、5461-10922、10923-16383。 key: user:{123}:profile 只哈希 {} 内内容 → "123" HASH_SLOT = CRC16(key) mod 16384 XMODEM CRC16 · 16 位取 14 位使用 slot = 5239(示意) 16384 个槽 = 键空间的固定分片坐标 16384 hash slots(不使用一致性哈希;槽是"键空间的固定分片坐标") slot 0 – 5460 slot 5461 – 10922 slot 10923 – 16383 Master A 负责 slot 0–5460 configEpoch 标识槽配置版本 Master B 负责 slot 5461–10922 cluster bus 端口 = 客户端口 + 10000 Master C 负责 slot 10923–16383 最小 3 主 · 推荐 3主3从 6 节点 Replica A1(复制 A) A 挂 → A1 参选晋升 Replica B1(复制 B) 主从模型 = 高可用内建于分片 Replica C1(复制 C) 节点 ID 160bit 随机 · 与 IP 无关 稳定态:每个槽恰好由 1 个主节点服务;扩缩容 = 在节点间移动槽(keys 随 MIGRATE 搬运,全程不停服) 16384 槽同时是主节点数上限(实际建议 ≤1000 节点)· 仅支持 db0,SELECT 不允许
Cluster 的核心模型一图讲完:key 先取 hash tag 里的大括号内容,再算 CRC16 模 16384 得到槽号,槽是键空间的固定分片坐标,跟节点解耦。三个主节点各管一段槽,每个主节点挂一个从节点,高可用直接内建在分片模型里。两个常被忽略的点:cluster bus 是独立端口,客户端端口加一万;Cluster 只有 db0,SELECT 是不允许的。扩缩容就是搬槽,keys 用 MIGRATE 原子搬运,全程不停服。

16384 Rationale · Gossip Protocol

为什么是 16384 槽 & 节点间的 gossip 四消息

为什么 16384 而不是 65536?(antirez 原答)

心跳包带完整槽位图:每个 PING/PONG 都携带本节点的槽 bitmap——16384 bit = 2KB,65536 bit = 8KB,gossip 里这个包每秒都在飞,×4 的带宽是"prohibitive"(禁止性开销);② 官方建议集群不超过 ~1000 个主节点(全连接 N-1、gossip 流量),16384 个槽给 1000 个节点分绰绰有余;③ CRC16 输出 16 位,只取 14 位(mod 16384)。

gossip 消息(cluster bus 二进制协议)

全连接 + gossip 传播(消息数不随 N 指数爆炸):正常时每秒随机 ping 少量节点;超 NODE_TIMEOUT/2 没联系过的节点必 ping。

消息语义要点
MEET邀请入群(CLUSTER MEET)与 PING 相同结构,但强制接收方把新节点当自己人——集群信任的起点,其余靠 gossip 链式传播
PING心跳探测 + 携带自身配置 + gossip 少量随机节点视图槽位图、configEpoch、节点 flags 都在包头;gossip 部分带几个随机节点状态
PONGPING 的回应也可主动单发(不等 ping)用于尽快广播新配置,如晋升后 mass-broadcast
FAIL节点死亡的全局广播PFAIL(本地判定:超过 node-timeout 无 PONG)→ 多数 master 确认 → 广播 FAIL,强制所有节点标记——触发从库选举
对比记忆:哨兵的客观下线叫 odown(quorum 个哨兵确认),Cluster 的叫 PFAIL→FAIL(多数 master 确认后广播)——两套高可用是同一"本地怀疑 + 多数确认 + 全局广播"思想的两份实现。
为什么是一万六千三百八十四个槽,antirez 在 GitHub issue 2576 里给过原答:心跳包携带完整槽位图,一万六千个槽就是 2KB,六万五就变 8KB,gossip 下带宽开销是禁止性的;而且官方建议集群别超一千个主节点,槽再多没意义。gossip 四消息记 MEET PING PONG FAIL:MEET 是信任起点,FAIL 是 PFAIL 被多数 master 确认后的强制广播。最后用对比记忆法把哨兵和 Cluster 的故障检测串起来,同一思想两份实现。

Redirection & Smart Client

MOVED vs ASK:永久搬家与临时借宿

重定向含义客户端该做什么语义细节
-MOVED 3999 host:port槽 3999 已永久易主去新节点重发本条命令;并更新本地槽表(通常整表重拉 CLUSTER SLOTS/SHARDS)错误里带槽号和负责节点;客户端不更新槽表也能工作(每次都被重定向),但会退化成慢路径
-ASK 3999 host:port槽 3999 迁移中:这条 key 可能已在目标节点只对下一条命令:先向目标节点发 ASKING 再重发;不更新槽表"只借宿一次":槽还没搬完,下一条命令的 key 可能还在源节点——必须先试源节点

为什么需要 ASK 而不能只用 MOVED?

迁移中同一槽的 key 源/目标两处都有:ASK 让客户端"先源后目标"逐 key 兜底;若此时把槽表直接改成目标节点,还在源节点的 key 就会被打错。官方原文:ASK = "send only the next query to the specified node"。

smart client(智能客户端)

启动拉取槽表 → 本地算槽直连正确节点(正常路径零重定向)→ 收到 MOVED 时刷新槽表并重试 → 定期/事件驱动维护。redis-cli -c 是"每次都重定向"的笨客户端演示;生产用 go-redis/Jedis/lettuce 等 cluster 模式。

易错点:ASK 重定向必须先发 ASKING——目标节点上该槽处于 IMPORTING 状态,只有带 ASKING 标记(一次性)的请求才被受理;否则又被 MOVED 弹回去。
MOVED 和 ASK 的区别一句话:MOVED 是永久搬家,客户端要更新槽表;ASK 是迁移中的临时借宿,只对下一条命令生效,还要先发 ASKING 打一次性标记,槽表不能改。为什么不能只用 MOVED:迁移中同一个 key 源和目标两边都可能存在,先试源再按 ASK 兜底才能保证每条 key 都找得到。smart client 的价值是把正常路径做到零重定向,收到 MOVED 再整表刷新。这套语义讲清楚,Cluster 客户端层的题就通了。

Replica Election · Resharding

Cluster 的可用性:从库选举与槽迁移

从库选举(比哨兵更 Raft)

主库被标 FAIL 后,从库按复制 offset 排名(rank 0 = 数据最新)错峰发起选举:DELAY = 500ms + rand(0~500ms) + rank × 1000ms。向所有 master 请求投票(FAILOVER_AUTH_REQUEST),多数 master 授权 → 晋升并生成更大 configEpoch,广播"last failover wins"。

多数派保护(node timeout)

主库超 cluster-node-timeout(默认 15000ms)联系不上多数 master → 进入错误状态停止接受写——少数派分区最多写 node-timeout 这么久。相关参数:cluster-replica-validity-factor 10(断链过久不参选)、cluster-require-full-coverage yes(槽没全覆盖就拒写)。

扩缩容五步(搬槽 8 从 A 到 B)说明
① B:SETSLOT 8 IMPORTING A目标节点标记"正在从 A 收槽"
② A:SETSLOT 8 MIGRATING B源节点标记"正在向 B 迁槽"——此后 A 对该槽不存在的 key 回 ASK 指向 B
③ A:CLUSTER GETKEYSINSLOT 8 n分批取该槽的 key
④ A → B:MIGRATE ... KEYS k1 k2原子搬运:序列化传输 + 两端短暂锁定 + 删源——任一时刻 key 只在 A 或 B
⑤ 双方 + 全体:SETSLOT 8 NODE B恢复正常态;全体发是为免等 gossip 自然收敛。迁移中跨两节点的多 key 命令返回 -TRYAGAIN
Cluster 的从库选举比哨兵更像 Raft:按 offset 排名错峰发起,延迟公式五百毫秒加随机加 rank 乘一千,数据最新的先选;拿多数 master 投票晋升,用更大的 configEpoch 赢下配置竞争。多数派保护靠 node timeout:联系不上多数 master 的主库十五秒后停写,这就是 Cluster 版的防脑裂。搬槽五步要能画出来:IMPORTING、MIGRATING、取 key、MIGRATE 原子搬运、SETSLOT NODE 收尾,迁移中跨槽多 key 会吃 TRYAGAIN。

Hash Tags · Limits · Trade-offs

跨槽限制、hash tag 与三种部署方案对比

跨槽限制与 hash tag

多 key 命令 / 事务 / Lua 涉及的所有 key 必须同槽(否则 CROSSSLOT 错误)。hash tag 强制同槽:key 含 {...}只哈希大括号内第一段内容——{user1000}.following{user1000}.followers 必同槽。规则边角:foo{}{bar} 整 key 哈希({} 内为空);foo{{bar}}zap 哈希的是 "{bar"。

hash tag 的代价

把业务对象绑到同一槽 = 放弃该对象在槽间的负载均衡;热点用户的 tag 会让某槽变成热槽。实践:以"业务聚合单元"(user/order)为 tag 粒度,而不是大客户 ID 直接当 tag。SCAN 类全库遍历需客户端逐槽聚合;KEYS 只在单节点视角生效。

维度主从 + 哨兵Cluster客户端分片(hash 取模/一致哈希)
容量 / 写扩展单主写,纵向为主多主分片,写近线性扩展(≤1000 节点)可扩展,但扩容要大规模搬数据(取模)或依赖预分片
高可用哨兵自动 failover(多数派授权)内置:FAIL 广播 + 从库选举自己搭(每片再挂从库 + 自研/外置切换)
客户端复杂度低:连哨兵问主库地址中:cluster 模式客户端(槽表 + 重定向)高:分片逻辑全在业务侧
多 key / 事务 / Lua无限制仅同槽(hash tag 缓解)跨实例不可行(需业务自己保证)
运维与成熟度简单,规模小标配官方方案;运维复杂度高(槽/迁移/广播)-proxy(如 Codis/自研)增加一层故障点
适用规模数据 < 单机内存、写 QPS 单主可扛数据/写压力超出单机,需要水平扩展历史方案为主;新项目首选官方 Cluster
Cluster 的第一限制是跨槽:多 key 命令、事务、Lua 全要同槽,hash tag 是官方给的逃生门,只哈希大括号里第一段内容。但 tag 也有代价,热点用户的槽会变成热槽,tag 粒度要选业务聚合单元。对比表按六个维度背:容量、高可用、客户端复杂度、多 key 能力、运维成本、适用规模。答题结论要明确:数据量和写压力没到单机上限就用主从加哨兵,别一上来就 Cluster——它牺牲了多 key 事务的便利换水平扩展。

Interview QA · 1/2

主从与哨兵 8 连问

1 · 主从复制是同步还是异步?会丢数据吗?

异步WAIT 缓解无法根除

异步:主库不等从库 ack 就回客户端。已确认的写在 failover 时可能丢(晋升了没收到写的从库)。WAIT 可要求 N 副本 ack,但官方明说它不能变成 CP 系统——只能大幅缩小概率。

2 · 全量同步流程?主从各干什么?

bgsave缓冲补发一次服务多从库

主库:BGSAVE 生成 RDB + 缓冲新写命令 → 回 +FULLRESYNC → 传 RDB → 补发缓冲命令。从库:清空旧数据 → 加载 RDB(短暂阻塞)→ 接收增量。多个从库并发请求只做一次 BGSAVE。

3 · 部分重同步怎么判断?三个要素各管什么?

replidoffsetbacklog

replid 匹配(主/副 ID 都认)且 offset 仍在主库 backlog 环形缓冲范围内 → +CONTINUE 只补增量;否则 +FULLRESYNC。replid 标记数据历史,offset 是逻辑时钟,backlog 是断线容忍的预算。

4 · backlog 设多大?默认值是多少?

默认 1mb写速率×断线时长

默认 1MB(redis.conf 8.2),生产必调。估算:平均写速率 × 预期最长断线时间,留 2 倍余量——如 5MB/s × 60s → 至少 300MB。repl-backlog-ttl 3600:无从库 1 小时后释放。

5 · 全量同步为什么会"风暴"?怎么破?

replica 缓冲超限循环全量

同步期间主库还要服务写,从库输出缓冲涨过 client-output-buffer-limit replica(256mb/64mb 60s)→ 断开 → 重新全量 → 再超限,死循环。破法:无盘复制、低峰同步、调大缓冲限制、控制单库规模。

6 · 哨兵的 quorum 和 majority 有什么区别?

quorum 管检测majority 管执行

quorum(每 master 配置)只决定 sdown 是否升级 odown——确认"真挂了";实际执行故障转移需 leader 获得哨兵多数派授权。5 哨兵 quorum=2:2 个就能确认下线,仍需 3 个授权才能切换。

7 · 哨兵怎么选新主?

先淘汰priority→offset→runid

先淘汰:断线超 down-after-ms×10 + sdown 时长的不合格。再三级排序:replica-priority 小者优先(0=永不晋升)→ replication offset 更大者优先 → runid 字典序小者优先(只为确定性)。

8 · 什么是脑裂?怎么缓解?还能丢吗?

少数派旧主min-replicas只能缩小窗口

旧主被隔离在少数派分区继续接受写,恢复后被降级清空——期间写入全丢。缓解:min-replicas-to-write 1 + min-replicas-max-lag 10,旧主 10 秒内拒写。仍有两个窗口:拒写前延迟 + 异步复制固有间隙——AP 系统丢数据无法根除。

主从这组的关键数字:backlog 默认 1MB、副本缓冲 256MB 64MB 60 秒、断线容忍估算公式。第五题的全量风暴是真实事故题,要能讲出缓冲超限断开重连的死循环。第六题 quorum 与 majority 是哨兵的灵魂题,检测和执行两级门槛分开说。第八题脑裂收尾必须落到现在 min-replicas 只是缩小窗口,这体现你对系统性质的理解而不是背配置。

Interview QA · 2/2

Cluster 与综合 8 连问

9 · Cluster 怎么定位 key?为什么是 16384 槽?

CRC16 mod 16384心跳 2KB≤1000 节点

HASH_SLOT = CRC16(key) mod 16384(XMODEM CRC16 取 14 位)。antirez 原答:心跳包带完整槽位图,16384 bit=2KB,65536 bit=8KB 属禁止性带宽开销;且集群不建议超 1000 主节点,槽再多无意义。

10 · MOVED 和 ASK 的区别?

永久易主迁移中临时ASKING

MOVED:槽已永久易主,客户端更新槽表再去新节点。ASK:槽迁移中,只对下一条命令生效,须先发 ASKING 打一次性标记,不更新槽表——因为下一条的 key 可能仍在源节点,必须先试源。

11 · Cluster 扩容(加节点)怎么搬数据?

IMPORTING/MIGRATINGMIGRATE 原子

目标 IMPORTING + 源 MIGRATING → GETKEYSINSLOT 分批取 key → MIGRATE 原子搬运(序列化+两端短暂锁定+删源,key 任一时刻只在一边)→ SETSLOT NODE 收尾。全程不停服;迁移中跨节点多 key 命令返回 TRYAGAIN。

12 · Cluster 里事务 / Lua / 多 key 命令怎么用?

必须同槽hash tag热槽代价

所有 key 必须同槽否则 CROSSSLOT 错。hash tag:key 中 {} 内第一段参与哈希,{user1000}.a 与 {user1000}.b 同槽。代价是该聚合单元放弃槽间均衡,热点 tag 造成热槽——tag 用业务聚合单元粒度。

13 · Cluster 从库怎么选举?与哨兵有何不同?

rank 错峰master 投票configEpoch

主库 FAIL 后从库按 offset 排 rank,延迟 = 500ms + rand(0~500) + rank×1000ms 后发起选举,向全部 master 请求投票,多数授权即晋升,生成更大 configEpoch。不同:投票者是 master 节点而非哨兵;选举内建于 gossip,无需独立进程。

14 · 主从+哨兵和 Cluster 怎么选?

单机可扛就别 Cluster多 key 代价

数据量与写压力在单机范围内 → 主从+哨兵(客户端简单、事务 Lua 无限制)。超出单机内存或写上限、需要水平扩展 → Cluster(牺牲跨槽多 key/事务,客户端要 cluster 模式)。客户端分片是历史方案,新项目不选。

15 · Cluster 会丢写吗? minority side 有什么约束?

异步复制node-timeout 停写

会:异步复制 + last failover wins。保护:主库联系不上多数 master 超过 cluster-node-timeout(默认 15s)即停写——少数派最多写 15 秒;多数派侧恢复需 FAIL 广播 + 从库选举(通常 1~2 秒)。cluster-require-full-coverage yes 时槽未全覆盖也拒写。

16 · 从库可以读吗?过期键怎么传播?

replica-read-only主库 DEL 传播

可以:replica-read-only 默认 yes;Cluster 从库需先发 READONLY 才能读可能过期的数据。过期键:从库不主动删,主库过期/淘汰时合成 DEL 传播;从库用逻辑时钟对读"隐藏"已过期键,晋升主库后才开始独立删除。

Cluster 这组的杀手锏在细节:第九题报出 antirez 原答的两 KB 心跳论证;第十题讲清 ASKING 的一次性标记;第十三题的 rank 延迟公式是读源码级细节;第十五题把 Cluster 和哨兵的防脑裂对应起来,node timeout 就是 Cluster 版的拒写开关。最后一题过期键传播和主从部分呼应:从库不删靠主库 DEL,这套设计避免了主从时钟依赖。

Related · Cross-links

相关知识点

Redis 领域 · 同批 deck

persistence · RDB/AOF/混合持久化(全量同步的 RDB 就是 BGSAVE 产物;RDB 存 replid 支撑部分重同步)
cache-patterns · 缓存三大问题与双写一致性(主从读扩展与热 key 治理联动)
distributed-lock · Redis 分布式锁(主从异步复制正是锁失效与 Redlock 之争的根源)
thread-model · 单线程模型(复制流发送、gossip 收发的执行者)

跨领域 · 答题串联

MySQL 主从复制:异步复制 + 丢写窗口两平台同源;Redis 用 backlog 补增量 ↔ MySQL 用 binlog position/GTID
通用线索:异步复制的可用性代价多数派授权防脑裂分片坐标与数据解耦(slot ↔ 分库分表路由)
分布式系统对照:哨兵/Cluster 的 epoch ↔ Raft term——"本地怀疑 + 多数确认"是通用故障检测范式

收尾串联:全量同步的 RDB 就是持久化那本 deck 里的 BGSAVE 产物,RDB 里存的 replid 让从库重启还能部分重同步;分布式锁那本的锁失效根源正是这里的异步复制。跨领域最有价值的是 MySQL 复制对照:两边的异步复制都有丢写窗口,Redis 用 backlog 补增量,MySQL 用 binlog 位点或 GTID。

References

参考来源

本 deck 全部结论可溯源至下列一手材料——数字与版本结论以原文为准。

redis.io/docs/latest/operate/oss_and_stack/management/replication/三大机制、full sync 流程、replid/offset 语义、主副 replid、diskless、min-replicas、过期键传播
redis.io/docs/latest/operate/oss_and_stack/management/sentinel/SDOWN/ODOWN、quorum vs majority、leader 选举与 config epoch、replica selection(priority/offset/runid)、TILT、Consistency under partitions(脑裂场景原文)
redis.io/docs/latest/operate/oss_and_stack/management/scaling/ · reference/cluster-spec/16384 slot、CRC16 mod 16384、hash tag 规则、MOVED/ASK、MIGRATING/IMPORTING、gossip 四消息、replica election(rank 延迟公式)、node timeout
github.com/redis/redis/issues/2576antirez 解释为什么 16384 槽:心跳槽位图 2KB vs 65536 的 8KB、~1000 节点上限
github.com/redis/redis · 8.2 redis.conf默认值核对:repl-backlog-size 1mb、repl-backlog-ttl 3600、repl-diskless-sync yes/delay 5、repl-timeout 60、client-output-buffer-limit replica 256mb 64mb 60、replica-priority 100、min-replicas-to-write 0 / min-replicas-max-lag 10、cluster-node-timeout 15000、cluster-replica-validity-factor 10、cluster-migration-barrier 1、cluster-require-full-coverage yes
所有默认值都对着 8.2 的 redis.conf 逐项核对过,16384 槽的论证出处是 antirez 在 GitHub issue 2576 的原答——面试报出这两个出处就够支撑前面所有数字。