Redis(远程字典服务)主从、Sentinel(哨兵)与 Cluster(集群)
这一章解决一个面试中最常被混为一谈的问题:复制能提供副本,Sentinel(哨兵)能在单主拓扑中自动切主,Cluster(集群)同时解决分片与局部故障转移;它们都不能把异步复制变成跨节点强一致。你需要能从路由、选主、复制偏移量和业务正确性四条线说明边界。
目录
前三个
kb:knowledge小节依次建立拓扑选型、异步复制确认,以及 Sentinel(哨兵)下线判定的基础;后续章节在此基础上展开选举、槽位路由、迁槽和一致性边界。
1. 架构选择、可靠性目标与面试主线
1.1 单机、主从、Sentinel(哨兵)与 Cluster(集群)对比
单机只有一个进程,部署简单但进程、机器或可用区故障都会中断服务。主从通过异步复制提供副本和读扩展,却需要人工或外部系统处理主节点失效。Sentinel(哨兵)为一个主从组增加监控、投票、选举和客户端地址发现,适合容量仍能装进一个主节点的高可用需求。Cluster(集群)把键空间分为 16384 个 slot(槽),每个主节点拥有一部分槽位,既横向扩容,又让每个分片配从节点做故障转移;它不是把同一键自动拆开,也不支持任意多键跨槽原子操作。
flowchart LR
A["单机\n单进程承载全部键"] --> B["主从\n异步复制与读扩展"]
B --> C["Sentinel(哨兵)\n自动发现与切主"]
C --> D["Cluster(集群)\n槽位分片与分片故障转移"]
D --> E["业务正确性\n仍由权威存储、幂等与对账兜底"]| 拓扑 | 主要解决的问题 | 写入路径 | 失效后的恢复 | 明确边界 |
|---|---|---|---|---|
| 单机 | 开发、低风险缓存 | 客户端直写唯一节点 | 人工重启或恢复 | 无副本、无自动切换 |
| 主从 | 冗余、读扩展 | 主节点写,从节点异步追赶 | 人工提升或外部编排 | 写确认不等于副本确认 |
| Sentinel(哨兵) | 单主自动高可用 | 主节点写 | 多个 Sentinel(哨兵)投票并选主 | 不能分片,也可能丢最近写 |
| Cluster(集群) | 分片、扩容、局部高可用 | 按 slot(槽)路由到所属主节点 | 对失效分片的从节点提升 | 跨槽限制、热点键不能被自动拆分 |
数据演绎 1:容量先决定拓扑。 某 WMS(仓储管理系统)热数据为 180 GiB(吉字节),预留复制缓冲、内存碎片、后台持久化写时复制和 30% 增长后,单主需要约 180 × 1.3 + 40 = 274 GiB(吉字节)。即使该单主加 Sentinel(哨兵),容量和故障恢复时间仍集中在一台机器;若拆为 6 个主分片,每片约 46 GiB(吉字节)业务数据,故障域和恢复工作量才可控。反过来,只有 8 GiB(吉字节)数据但必须自动切换时,先用 Sentinel(哨兵)往往比过早引入 Cluster(集群)更简单。
热门面试题
问题(基础题):什么时候选单机、主从、Sentinel(哨兵)或 Cluster(集群)?
- 考点:容量、可用性与运维复杂度的匹配。
- 回答思路:先给业务目标,再逐层增加能力。
- 详细答案:单机适合可丢、可重建且故障可接受的低风险缓存;主从增加副本和读扩展,但主故障仍要人工接管;需要单主自动故障转移时使用 Sentinel(哨兵);当容量、吞吐或恢复时间已经不能由一个主节点承担时,再使用 Cluster(集群)分片。选型不以“功能更多”为目标,而以数据规模、可接受中断、跨槽访问比例和团队运维能力为依据。
- 进阶追问:Cluster(集群)是否一定比 Sentinel(哨兵)高可用?
- 进阶回答:不一定。两者都依赖副本和故障检测;Cluster(集群)增加了分片可用性,却带来路由、迁槽和多分片运维复杂度。单分片只有一主一从时,副本同时失效仍不可用。
问题(原理题):为什么主从复制不能自动等同于高可用?
- 考点:数据副本与控制平面的区别。
- 回答思路:说明复制只传数据,不决定谁接写流量。
- 详细答案:主从复制维护的是数据流:主节点把写命令或复制流发送给从节点。从节点知道自己复制谁,却不会仅凭主节点短暂不可达就安全地宣布自己为主,因为网络分区可能让旧主仍在服务。高可用还需要独立的故障判定、投票、选举、旧主隔离、客户端重定向和副本重配置,这正是 Sentinel(哨兵)或 Cluster(集群)控制平面的职责。
- 进阶追问:手工提升从节点有哪些风险?
- 进阶回答:若未隔离旧主,旧主恢复后可能继续接写形成双主;若提升的是复制偏移量落后的副本,会丢失最近已确认写。因此手工操作也必须先围栏旧主、核对副本进度并重配客户端。
问题(项目追问题):如何给 WMS(仓储管理系统)库存读模型选择拓扑?
- 考点:正确性路径与加速路径分离。
- 回答思路:先说明库存权威事实,再选读模型拓扑。
- 详细答案:库存扣减以数据库条件更新、流水和业务唯一号为最终依据;Redis(远程字典服务)只承担库存展示、热点预判与削峰。若热集可放单机且允许短暂降级,单机或主从即可;若接口必须自动切换,用 Sentinel(哨兵);若仓库、货主和 SKU(库存单位)热集超过单机内存或吞吐,再按可路由键设计 Cluster(集群)。无论哪种拓扑,缓存显示有货都不能绕过数据库的
available >= quantity条件。 - 进阶追问:能否用 Cluster(集群)保证不超卖?
- 进阶回答:不能。槽位保证键的路由,不保证跨故障、异步落库或跨键业务状态的一致提交。缓存原子预扣最多是流量闸门,权威扣减、幂等和补偿仍要落在可审计系统中。
1.2 复制确认、可用性与一致性边界
默认复制是异步的:主节点先在本地执行命令并向客户端返回,再把复制流发送给从节点。因此“客户端成功”“从节点已收到”“从节点已执行”“从节点持久化”和“新主继承该值”是不同事实。WAIT(等待副本确认)可以等待指定数量副本确认某复制偏移量,缩小已确认写只存在于单主的窗口,但它不是事务提交协议:网络分区、进程故障、选主结果与磁盘策略仍能让最后写丢失。读从节点则还要承认复制延迟,不能将它用于刚写后必须读到新值的路径。
sequenceDiagram
participant C as "客户端"
participant M as "主节点"
participant R as "从节点"
C->>M: "写入订单状态"
M-->>C: "默认成功:本地执行完成"
M->>R: "异步复制流"
R->>R: "应用复制偏移量"
R-->>M: "确认偏移量"
M-->>C: "WAIT(等待副本确认)可观察确认数"| 机制 | 增强的保证 | 仍不能保证 | 适用方式 |
|---|---|---|---|
| 默认异步复制 | 有机会从副本恢复 | 返回写一定已复制 | 普通可重建缓存 |
| WAIT(等待副本确认) | 指定副本确认复制偏移量 | 副本已刷盘或必被选主 | 对少量关键短状态削减窗口 |
min-replicas-to-write(最少副本写入数) | 副本数量不足或延迟过大时拒绝新写 | 已返回写零丢失 | 对可拒绝写的保护性降级 |
| 从节点读 | 分担读压力 | 读己之写、单调读 | 可陈旧列表、轨迹摘要 |
数据演绎 2:确认数不是零丢失。 主节点 M 写入 pay:19=SUCCESS 后 1 毫秒返回,R1 在 4 毫秒确认,WAIT(等待副本确认)返回 1。第 5 毫秒 M 和 R1 同时断电,R2 只复制到旧偏移量且被提升,新主没有该状态。即便 R1 曾确认,也不能保证它能存活、被选中或已稳定落盘。对支付,权威订单和渠道流水必须在事务存储中确认;Redis(远程字典服务)状态只用于查询加速,客户端带更高已知版本时应回源而非相信从节点旧读。
热门面试题
问题(基础题):WAIT(等待副本确认)解决了什么,没解决什么?
- 考点:复制偏移量确认与持久化、选主的边界。
- 回答思路:明确它等待的是副本确认,不是全局提交。
- 详细答案:WAIT(等待副本确认)让当前连接等待指定数量从节点确认已经处理到某复制偏移量,适合降低主节点刚返回就单点故障的风险。它没有要求副本一定落盘,也没有锁定未来必须由该副本晋升,更不会让网络分区中的两个区域完成共识。因此它是风险缩减工具,不应被描述为强一致或资金正确性的唯一依据。
- 进阶追问:WAIT(等待副本确认)超时但后来副本收到写,客户端该如何处理?
- 进阶回答:超时只说明在等待窗口内未获得足够确认,不能推断写失败。客户端必须用业务幂等键和权威状态查询确认结果,绝不能盲目重放有副作用的请求。
问题(原理题):为什么从节点读会破坏“写后马上读到”?
- 考点:异步复制和路由不黏连。
- 回答思路:按主已执行、从尚未应用的时序说明。
- 详细答案:写请求在主节点执行后立即返回,而复制流还在网络、输入缓冲或从节点事件循环中。若下一次读被负载均衡到从节点,它可能仍处于较小复制偏移量,于是读到旧值或空值。即使本次追上,也不能保证下一次读仍落到同一副本;需要读主、会话黏连、版本下限回源或业务容忍陈旧来建立明确语义。
- 进阶追问:读取前调用 WAIT(等待副本确认)能完全解决吗?
- 进阶回答:它只等写时在线的确认数,读请求仍可能路由到另一个落后的副本,且故障切换可回退。因此关键状态读应访问权威主路径或携带版本校验。
问题(项目追问题):支付查询允许读从节点吗?
- 考点:按状态和风险分层读策略。
- 回答思路:区分终态查询、刚回调查询与资金决策。
- 详细答案:支付扣款、退款、出账和状态推进不能依赖从节点读;渠道回调刚落库后,用户立即查询也应读主库或按订单版本回源。对于数分钟前的终态列表、报表汇总,可从副本读取并展示更新时间。设计的重点不是“能否读从”,而是把允许陈旧的页面和不可陈旧的决策分开,监控复制延迟并在超过阈值时自动降级为读主或返回处理中。
- 进阶追问:副本读到旧的待支付状态会造成什么后果?
- 进阶回答:若页面仅展示,可能造成用户困惑;若业务据此再次发起扣款,则可能造成重复副作用。因此发起支付必须由权威状态机和唯一业务号判定,页面缓存不能成为决策依据。
2. Sentinel(哨兵):故障检测、投票与转移
2.1 主观下线、客观下线与 quorum(法定票数)
每个 Sentinel(哨兵)独立向主节点、从节点和其他 Sentinel(哨兵)发送心跳。一个 Sentinel(哨兵)在 down-after-milliseconds(下线判定毫秒数)内持续得不到有效响应,会把目标标记为 SDOWN(主观下线):这只是“我认为它不可达”,可能是本 Sentinel(哨兵)自身网络异常。对于被监控的主节点,Sentinel(哨兵)会向其他 Sentinel(哨兵)询问判断;达到配置 quorum(法定票数)后标记 ODOWN(客观下线),才进入故障转移候选流程。quorum(法定票数)是发现故障的阈值,不等同于选举领导者所需的多数票。
flowchart TD
S1["Sentinel(哨兵)A 心跳超时"] --> SD["SDOWN(主观下线)"]
SD --> ASK["向其他 Sentinel(哨兵)询问"]
ASK --> Q{"达到 quorum(法定票数)?"}
Q -->|否| MON["继续监控,保留本地怀疑"]
Q -->|是| OD["ODOWN(客观下线)"]
OD --> EL["请求领导者选举"]
EL --> FO["故障转移"]| 状态或阈值 | 产生者 | 含义 | 常见误解 |
|---|---|---|---|
| SDOWN(主观下线) | 单个 Sentinel(哨兵) | 本地连续探测失败 | 不是全局事实,不能立刻切主 |
| ODOWN(客观下线) | 满足询问结果的 Sentinel(哨兵) | 足够 Sentinel(哨兵)同意主不可达 | 不代表所有节点都看不到主 |
| quorum(法定票数) | 监控配置 | 认定 ODOWN(客观下线)的最小同意数 | 不等于领导者选票数 |
| majority(多数) | Sentinel(哨兵)总数 | 领导者授权常需的多数 | 不能只部署两个 Sentinel(哨兵)期待抗分区 |
数据演绎 3:3 个 Sentinel(哨兵)与 quorum(法定票数)2。 S1 到主节点网络中断,S2、S3 正常:S1 只能形成 SDOWN(主观下线),询问后拿不到第二个同意,不会切换。若主机实际宕机,S1、S2 都超时,S1 从 S2 获得同意,达到 2 形成 ODOWN(客观下线)。若只部署 2 个 Sentinel(哨兵)且分别在两个机房,任何一个机房隔离都无法获得多数来安全选主;把 quorum(法定票数)调成 1 虽可能更快,却会把单点网络故障放大成错误切换。
热门面试题
问题(基础题):SDOWN(主观下线)和 ODOWN(客观下线)有什么区别?
- 考点:本地观测与多节点确认。
- 回答思路:先说明单节点网络故障,再说明投票收敛。
- 详细答案:SDOWN(主观下线)是一个 Sentinel(哨兵)在超时窗口内无法和目标建立有效通信后的本地判断;它可能由本机停顿、局部网络或目标故障造成。ODOWN(客观下线)是针对主节点,在该 Sentinel(哨兵)向其他 Sentinel(哨兵)查询后,获得不少于 quorum(法定票数)的同意而形成的协作判断。区分二者避免单观察者把局部故障误升级为全局切换。
- 进阶追问:为什么从节点通常不需要 ODOWN(客观下线)?
- 进阶回答:从节点失效不会改变写入口,不需要触发选主;Sentinel(哨兵)主要记录其状态并在主故障时从剩余候选中选择。主节点失效才会影响服务角色与客户端写路径。
问题(原理题):quorum(法定票数)为什么不等于多数?
- 考点:故障发现与领导者授权的两阶段门槛。
- 回答思路:分别解释 ODOWN(客观下线)和领导者选举。
- 详细答案:quorum(法定票数)是当前 Sentinel(哨兵)向同行询问主节点状态时,判定 ODOWN(客观下线)需要的同意数量;它可小于 Sentinel(哨兵)总数的一半。故障转移还要有一个 Sentinel(哨兵)在当前纪元获得多数 Sentinel(哨兵)的领导者授权,防止多个观察者并发执行不同转移。前者追求足够快地发现,后者追求单一执行者,两者解决不同风险。
- 进阶追问:能把 quorum(法定票数)设为 1 吗?
- 进阶回答:配置上可能允许,但它意味着单个 Sentinel(哨兵)的局部误判就可形成 ODOWN(客观下线)候选,增加抖动和错误转移概率。生产应根据故障域、Sentinel(哨兵)数量和网络质量设置,并保持奇数个独立实例。
问题(项目追问题):怎样设置 IoT(物联网)报警平台的 Sentinel(哨兵)超时?
- 考点:快速恢复与误切换成本。
- 回答思路:用真实尾延迟和业务降级时间反推。
- 详细答案:先采集 Redis(远程字典服务)心跳、网络往返、进程停顿和故障恢复的高分位,而不是凭经验设一个很小值。报警风暴时应用和网络都可能短暂拥塞,过短的
down-after-milliseconds(下线判定毫秒数)会让局部抖动频繁触发转移;过长则延长告警去重、限流状态不可用的时间。通常部署跨故障域的三个 Sentinel(哨兵),设置能覆盖常态尖峰的超时,再用限流降级保护下游。 - 进阶追问:发生误切换后最先检查什么?
- 进阶回答:对齐 Sentinel(哨兵)日志中的心跳超时、同行询问、纪元与领导者投票,并关联网络丢包、宿主机暂停和主节点慢事件;先判断是主真的故障、局部网络隔离还是阈值不合理。
2.2 Sentinel(哨兵)领导者选举与故障转移
形成 ODOWN(客观下线)后,Sentinel(哨兵)会递增配置纪元并请求其他 Sentinel(哨兵)在该纪元投票。每个 Sentinel(哨兵)在一个纪元通常只投一票,获得多数票的候选成为领导者,负责执行一次故障转移:挑选从节点、发送提升命令、等待角色转换、让其他从节点复制新主、更新配置并通知客户端。候选副本优先考虑复制健康、断连时间、复制偏移量、优先级和标识;并非单纯选择“IP(网络地址)最小”的节点。旧主回来后会被配置为新主的从节点,避免双写长期存在。
sequenceDiagram
participant L as "当选 Sentinel(哨兵)领导者"
participant R as "候选从节点"
participant O as "其他从节点"
participant C as "客户端"
L->>R: "提升为新主节点"
R-->>L: "角色转换完成"
L->>O: "复制新主节点"
L->>C: "发布新主地址"
C->>R: "重连并恢复写入"
Note over L,R: "旧主恢复后被重配为从节点"| 转移步骤 | 领导者动作 | 成功证据 | 失败或风险 |
|---|---|---|---|
| 选举 | 获得当前纪元多数授权 | 领导者日志、纪元一致 | 多数不可达则不能安全继续 |
| 选副本 | 排除长期断连或落后候选 | 候选复制偏移量、优先级 | 最新副本也可能缺最后写 |
| 提升 | 让候选停止复制并成为主 | 新主角色和监听状态 | 提升超时、节点资源不足 |
| 重配 | 其他副本转向新主 | 复制链路恢复 | 部分副本滞后或不可达 |
| 通知 | 更新发现信息并让客户端重连 | 新地址连接成功率 | 旧连接、硬编码地址导致失败 |
数据演绎 4:选最新副本仍会丢数据。 原主复制偏移量为 10,000,R1 为 9,998,R2 为 9,995;最后两条写已返回给客户端却还未送到任何副本。故障发生后 Sentinel(哨兵)会选择 R1,因为它最接近主,但新主最大也只能恢复到 9,998,至少丢两条写。这是异步复制的理论边界;min-replicas-to-write(最少副本写入数)只能在副本不足时拒绝未来写,无法补回已经确认但未复制的写。
热门面试题
问题(基础题):Sentinel(哨兵)如何选出执行故障转移的领导者?
- 考点:配置纪元、单票与多数授权。
- 回答思路:按“请求投票—每纪元一票—多数胜出”说明。
- 详细答案:当主节点被判定 ODOWN(客观下线)后,Sentinel(哨兵)在新的配置纪元请求同行授权。每个 Sentinel(哨兵)对同一纪元只授予一个候选,获得多数授权的候选成为领导者并执行转移。这样即使多个 Sentinel(哨兵)同时检测到故障,也能收敛到一个执行者,减少并发提升不同副本的风险。
- 进阶追问:没有任何候选拿到多数会怎样?
- 进阶回答:不会安全地完成自动故障转移,系统继续等待可通信的 Sentinel(哨兵)恢复或由人工按围栏流程介入。这是为了避免网络分区时两个区域各自宣布新主。
问题(原理题):Sentinel(哨兵)选新主时为什么关注复制偏移量?
- 考点:复制进度与可恢复数据量。
- 回答思路:偏移量越大表示已应用的复制流越多。
- 详细答案:从节点复制偏移量表示它已处理到复制流的哪个位置。故障转移时,复制健康且偏移量更大的副本通常拥有更多原主已执行的数据,因此优先选择它能缩小回退窗口。它只是启发式选择而非提交证明:最后写可能尚未到任何副本,且偏移量接近也不能说明业务状态完整或已落盘。
- 进阶追问:副本优先级能覆盖偏移量选择吗?
- 进阶回答:可以通过优先级表达机房、硬件或角色偏好,但人为偏好过高会选择更落后的副本。生产要把优先级作为故障域约束,并持续监控复制延迟与偏移量差。
问题(项目追问题):故障切换期间 Runner(执行器)任务如何避免重复执行?
- 考点:缓存高可用不等于任务幂等。
- 回答思路:把租约、执行事实和副作用拆开。
- 详细答案:Runner(执行器)可以用 Redis(远程字典服务)短租约降低并发抢占,但任务完成事实、业务唯一键和状态迁移必须持久化。切换时旧主可能丢租约写或旧客户端仍短暂连接,两个执行器都可能认为自己获得资格;下游写入应通过任务实例号、唯一约束或 fencing token(栅栏令牌)拒绝旧执行者。故障恢复后按持久化状态扫描超时任务并幂等补偿,而不是相信缓存锁一定存在。
- 进阶追问:为什么仅延长租约不能解决?
- 进阶回答:长租约会增加故障后的不可接管时间,短租约会增加暂停后误续约风险;无论时长,网络分区和复制回退都可能让锁状态不可靠,必须有受保护资源侧的版本或唯一约束。
3. Sentinel(哨兵)的脑裂、数据丢失与配置防线
3.1 脑裂发生条件与旧主围栏
脑裂不是两个节点“都认为自己健康”这么简单,而是旧主仍能接收部分客户端写,同时 Sentinel(哨兵)在另一侧把从节点提升为新主,出现两个可写历史。典型条件是网络分区:旧主与 Sentinel(哨兵)多数失联,却仍连接到一部分硬编码或未及时刷新地址的客户端。故障转移完成后,新主接受写;旧主隔离区继续接受写。网络恢复时,旧主通常会按新配置降为从节点并丢弃其分叉数据,因此隔离区期间的写可能永久消失。
flowchart TB
subgraph X["隔离区 A"]
OM["旧主节点"] --> OC["未刷新地址的客户端"]
end
subgraph Y["多数区 B"]
SS["Sentinel(哨兵)多数"] --> NM["新主节点"]
NC["正常客户端"] --> NM
end
SS -. "无法探测旧主" .-> OM
OM -. "网络恢复后降级并同步" .-> NM
OC --> LOSS["旧主分叉写被覆盖或丢弃"]| 脑裂放大因素 | 为什么危险 | 防线 |
|---|---|---|
| 客户端硬编码主地址 | 切换后仍向旧主写 | 使用 Sentinel(哨兵)发现、重连与地址刷新 |
| 网络未隔离旧主 | 旧主持续提供写服务 | 网络围栏、负载均衡摘流、主机隔离 |
| 副本不足仍允许写 | 新主可能无足够副本冗余 | min-replicas-to-write(最少副本写入数) |
| 把缓存当权威事实 | 分叉写直接变成业务错误 | 权威数据库、幂等、版本与对账 |
数据演绎 5:脑裂丢写窗口。 10:00:00 网络分区,旧主仍服务仓库 A 的应用;10:00:08 Sentinel(哨兵)多数完成新主提升,仓库 B 写入库存展示版本 v102;10:00:10 仓库 A 仍向旧主写入 v101a;10:00:30 网络恢复,旧主被重配为从节点并全量或增量同步新主历史,v101a 不在新主复制流中而消失。若 v101a 被用于真实扣减且没有数据库流水,系统无法靠 Redis(远程字典服务)自证正确;因此缓存必须不承担库存最终扣减。
热门面试题
问题(基础题):Sentinel(哨兵)场景为什么会脑裂?
- 考点:网络分区、旧主继续写和客户端发现滞后。
- 回答思路:说明控制平面与数据平面看到的网络不同。
- 详细答案:当旧主与 Sentinel(哨兵)多数失联时,多数区会把它判定故障并提升新主;但旧主可能仍能服务隔离区客户端,尤其是客户端硬编码地址或没有正确订阅发现变更时。这样控制平面已产生新主,而部分数据平面仍把旧主当写入口,两个节点分别接收写,形成分叉。
- 进阶追问:网络恢复后为什么旧主写会丢?
- 进阶回答:旧主会按新的拓扑降级为从节点,并以新主的复制流为准追赶;它在隔离期间独自接受的写不在新主历史上,通常会被覆盖或丢弃,以恢复单一主复制链。
问题(原理题):客户端正确接入 Sentinel(哨兵)能完全杜绝脑裂吗?
- 考点:发现刷新只能缩小窗口,不能替代隔离。
- 回答思路:说明连接已经建立、DNS(域名系统)缓存和网络分区仍存在。
- 详细答案:正确使用 Sentinel(哨兵)地址发现与断线重连能显著减少客户端持续写旧主的概率,但不能保证所有连接立即断开。已有长连接、应用暂停、网络单向可达、负载均衡缓存和错误重试都可能让一段时间内请求仍到旧主。真正降低双写风险还要隔离旧主网络路径、在客户端识别角色变化并让业务副作用有幂等与围栏。
- 进阶追问:能否让旧主自行检测到多数失联就拒写?
- 进阶回答:
min-replicas-to-write(最少副本写入数)可在可用从节点数量或延迟不达标时拒绝写,能缩小孤岛旧主继续接写的窗口;但阈值、延迟与网络观测都不是共识提交,不能替代业务权威存储。
问题(项目追问题):IoT(物联网)报警去重键在脑裂后如何收敛?
- 考点:可重放事件与去重窗口的容错设计。
- 回答思路:将缓存去重视作优化,不视作唯一事实。
- 详细答案:报警原始事件先进入可持久化的消息与存储,Redis(远程字典服务)去重键只减少风暴期间的重复通知。脑裂导致去重键回退时,消费者仍按设备、规则、时间窗口和事件标识在权威表或幂等日志判重;回退导致的重复只会被拒绝或合并,不会重复升级工单。恢复后可从事件位点重建去重热集,并以重复通知率、漏告率和积压年龄验证收敛。
- 进阶追问:若权威判重性能不足怎么办?
- 进阶回答:可以分层:本地短窗去重、Redis(远程字典服务)分布式窗口和数据库唯一键兜底;并按设备或租户分片、批量判重和限流,不能因为性能压力删掉最后一道正确性防线。
4. 复制写保护与一致性边界
4.1 min-replicas-to-write(最少副本写入数)与拒写语义
min-replicas-to-write(最少副本写入数)与 min-replicas-max-lag(最少副本最大延迟)让主节点在“健康从节点数量不足”或“从节点延迟超过阈值”时拒绝新的普通写入。它的价值是避免孤立主节点在没有足够复制冗余时继续积累只存在于自身的成功写,从而缩小脑裂与主故障的可见丢失窗口。它不会等待每次写的确认,不会让已返回写回滚,也不会覆盖所有命令与所有业务路径;应用必须能够处理写拒绝、重试上限、降级和幂等查询。
flowchart LR
W["客户端写请求"] --> H{"健康副本数 ≥ 最少副本写入数\n且延迟 ≤ 最大延迟?"}
H -->|是| M["主节点执行并异步复制"]
H -->|否| R["主节点拒绝写入"]
R --> B["业务按幂等键查询、排队或降级"]
M --> C["仍可能在副本接收前主故障"]| 配置或策略 | 它保护什么 | 代价 | 不能替代什么 |
|---|---|---|---|
min-replicas-to-write(最少副本写入数) | 避免无足够健康副本时继续确认写 | 副本短暂滞后会直接拒写 | 跨节点提交协议 |
min-replicas-max-lag(最少副本最大延迟) | 排除落后太久的副本 | 阈值过低会放大抖动 | 磁盘持久化证明 |
| WAIT(等待副本确认) | 单次写等待副本确认 | 增加尾延迟、可能超时 | 必被选主与零丢失 |
| 业务唯一键与状态机 | 重试不产生重复副作用 | 需要权威存储设计 | 缓存的高吞吐能力 |
数据演绎 6:拒写换取丢失窗口上界。 主节点要求至少 1 个从节点且延迟不超过 2 秒。正常时 R1 延迟 15 毫秒,写入继续;网络故障后 R1 延迟超过 2 秒,主节点开始拒绝新写。此时订单查询缓存会出现可用性下降,但不会再把新的“成功”只留在孤立主上。若业务把每次拒写立即无上限重试,1,000 QPS(每秒查询率)会变成重试风暴;正确做法是按幂等号查询权威状态、有限退避、排队或明确返回稍后重试。
热门面试题
问题(基础题):
min-replicas-to-write(最少副本写入数)解决什么问题?- 考点:孤立主写入与保护性拒绝。
- 回答思路:说明它在写前检查副本健康度。
- 详细答案:该配置要求主节点只有在足够数量从节点、且这些从节点复制延迟不超过阈值时才接受写。它把“主节点仍活着但没有可靠副本”的状态从继续成功写改为拒绝写,降低主节点单点故障或网络孤岛时大量已确认写无法出现在未来新主上的风险。
- 进阶追问:它是否保证每一条成功写都在副本上?
- 进阶回答:不保证。检查的是写入前的副本健康状态,当前命令仍可能在复制给副本前主节点故障;需要更强确认可结合 WAIT(等待副本确认),但仍不是零丢失共识。
问题(原理题):为什么配置过严会降低可用性?
- 考点:异步副本延迟与拒写传播。
- 回答思路:从临时网络抖动、从节点负载和写入口拒绝解释。
- 详细答案:从节点的复制延迟会受网络、磁盘、事件循环和全量同步影响。阈值过小或要求副本数过多时,短暂抖动就会让主节点拒绝所有写,即便主节点本身可用。配置必须从允许丢失窗口、可承受拒写时间、故障域和实际延迟分位推导,并让应用把拒写当成可预期的业务结果而非无限重试信号。
- 进阶追问:缓存写被拒绝应直接写数据库吗?
- 进阶回答:取决于键的角色。可重建缓存可以跳过或异步补写;库存、支付等正确性操作本来就应先走权威数据库,不能把故障切换时的直写数据库误解为缓存方案失效。
问题(项目追问题):跨境物流轨迹摘要遇到拒写如何降级?
- 考点:允许陈旧读模型的可用性设计。
- 回答思路:保留事实事件,摘要可延迟重建。
- 详细答案:承运商事件先可靠写入事件库或消息系统,Redis(远程字典服务)摘要更新被拒绝时记录待重建任务而不阻塞事件入库。查询端可返回带更新时间的旧摘要,超过业务定义的硬陈旧阈值时回源轨迹库;恢复后按运单版本批量重建缓存。这样拒写影响读模型新鲜度,而不影响轨迹事实、顺序审计和后续补偿。
- 进阶追问:怎样防止恢复后旧事件覆盖新摘要?
- 进阶回答:缓存值携带单调业务版本,更新用 Lua(脚本语言)原子比较,只接受更高版本;乱序旧事件可保留历史,但不能把最新摘要倒退。
5. Cluster(集群)路由:槽位、重定向与同槽设计
5.1 16384 个 slot(槽)与 CRC16(循环冗余校验 16 位)路由
Cluster(集群)不是一致性哈希环,而是把键映射到固定的 16384 个 slot(槽)。客户端取键的 hash tag(哈希标签)内容;没有花括号或花括号为空时取整个键,计算 CRC16(循环冗余校验 16 位),再对 16384 取模得到槽号。集群元数据记录“每个槽由哪个主节点拥有”,客户端会缓存此映射并直接连接所属主节点。固定槽位让迁移单位明确:可以把部分槽从一个主节点转移给另一个主节点,而无需改变全部键的映射规则。
flowchart LR
K["键:{warehouse:9}:sku:88"] --> T["取 hash tag(哈希标签)\nwarehouse:9"]
T --> H["CRC16(循环冗余校验 16 位)"]
H --> S["结果对 16384 取模"]
S --> O["slot(槽)= 7421"]
O --> N["查询槽位所属主节点"]
N --> X["向目标主节点发送命令"]| 路由元素 | 规则 | 工程意义 | 易错点 |
|---|---|---|---|
| 键 | 默认全部参与哈希 | 键自然分布到多个槽 | 同业务实体的多键可能跨槽 |
| hash tag(哈希标签) | 取首对非空花括号内容 | 显式控制同槽 | 标签过粗会制造热点 |
| CRC16(循环冗余校验 16 位) | 对参与哈希字节计算 | 客户端与节点一致路由 | 不能自行改用其他散列算法 |
| 16384 个 slot(槽) | 结果取模后的固定空间 | 迁移、归属和统计有明确单位 | 槽数不是节点数,也不是键数 |
数据演绎 7:槽位均衡不等于流量均衡。 6 个主节点各持有约 2,731 个 slot(槽),总流量 120,000 QPS(每秒查询率),理想平均是每主 20,000 QPS(每秒查询率)。一个活动配置键 campaign:2026 却独占 45,000 QPS(每秒查询率),其所在节点实际接近 65,000 QPS(每秒查询率);即使把槽位数量重新均分,这个单键仍只能落在一个槽。治理应使用进程内短缓存、读副本、请求合并或按可接受的业务维度拆键,而不是只做槽位扩容。
热门面试题
问题(基础题):Cluster(集群)为什么选择 16384 个 slot(槽)?
- 考点:固定分片单位和元数据传播成本。
- 回答思路:说明槽位是键与节点之间的中间层。
- 详细答案:固定数量的 slot(槽)让集群不必在每次扩缩容时逐键广播全部映射,只需传播槽范围的归属变化;16384 个槽又足够细,能在常见节点规模下进行较平滑的再平衡。它是实现权衡而不是容量上限:每个槽可包含任意数量键,节点数也远小于槽数。
- 进阶追问:一个节点可以拥有不连续槽位吗?
- 进阶回答:可以。槽位归属是集合,不要求连续;迁槽和再平衡会形成多个范围。客户端依据完整槽位映射路由,而不是假设节点负责一个连续区间。
问题(原理题):hash tag(哈希标签)为什么能让多个键落到同一槽?
- 考点:参与 CRC16(循环冗余校验 16 位)的字符串改变。
- 回答思路:以同一花括号内容为哈希输入解释。
- 详细答案:Cluster(集群)对键名中的首个非空花括号内容计算哈希;例如
{order:42}:header与{order:42}:items都只对order:42计算 CRC16(循环冗余校验 16 位),因而得到相同槽位。这样同槽多键命令和事务可以执行,但它只保证路由同处,不保证业务跨对象的一致性。 - 进阶追问:能否把所有键都写成
{all}? - 进阶回答:技术上可以,但会让全部键集中到一个槽和一个主节点,失去分片能力并制造超级热点。标签应限定到需要原子协作的最小业务聚合。
问题(项目追问题):WMS(仓储管理系统)的库存键如何设计 hash tag(哈希标签)?
- 考点:同槽原子操作与热点分散的平衡。
- 回答思路:以库存聚合维度作为最小标签。
- 详细答案:如果一次 Lua(脚本语言)预校验必须同时读取可用量、冻结量和版本,可将它们命名为
{wh:9:owner:2:sku:88}:available、{wh:9:owner:2:sku:88}:frozen与版本键,使单个库存聚合落在同槽。不要把整个仓库或全部 SKU(库存单位)放进同一标签,否则活动热点会集中。真正扣减仍在权威数据库做条件更新,缓存同槽原子性只用于减少无效竞争。 - 进阶追问:跨两个仓库调拨怎么办?
- 进阶回答:不能依赖跨槽脚本假装原子。应在权威系统用状态机记录出库、在途和入库步骤,采用可重试补偿;缓存分别更新两个聚合的读模型。
5.2 MOVED(永久重定向)、ASK(临时重定向)与跨槽边界
当客户端把命令发送到不拥有目标槽的节点,稳定归属变化会返回 MOVED(永久重定向),携带目标槽与节点地址,客户端更新槽位缓存并重试。槽位迁移期间,源节点标记 MIGRATING(迁出中),目标节点标记 IMPORTING(导入中);已迁移键可能要求客户端先向目标节点发送 ASKING(允许访问导入槽)再执行命令,源节点返回 ASK(临时重定向)。ASK(临时重定向)不应更新长期槽位缓存,因为迁移尚未完成。多键命令若键不在同一槽会得到 CROSSSLOT(跨槽错误),这是协议边界,不是客户端重试能解决的问题。
sequenceDiagram
participant C as "客户端"
participant A as "旧槽位节点"
participant B as "新槽位节点"
C->>A: "访问迁移中的键"
A-->>C: "ASK(临时重定向) B"
C->>B: "ASKING(允许访问导入槽)"
C->>B: "重发原命令"
B-->>C: "返回键值"
Note over C,B: "迁移完成后才以 MOVED(永久重定向)刷新缓存"| 返回或错误 | 发生时机 | 客户端正确动作 | 禁止做法 |
|---|---|---|---|
| MOVED(永久重定向) | 槽稳定归属已变 | 更新槽位缓存并重试目标节点 | 永久继续访问旧节点 |
| ASK(临时重定向) | 键处于迁移窗口 | 发送 ASKING(允许访问导入槽)后单次重试 | 把目标写入长期槽位缓存 |
| CROSSSLOT(跨槽错误) | 多键不在同一槽 | 改键模型、拆步骤或使用 hash tag(哈希标签) | 无限制重试同一命令 |
| TRYAGAIN(稍后重试) | 迁移或短暂状态不稳定 | 有上限退避重试 | 把重试变成流量放大器 |
数据演绎 8:错误处理放大。 迁移 500,000 个键时,应用有 5,000 QPS(每秒查询率)。若客户端把每个 ASK(临时重定向)误当 MOVED(永久重定向)缓存,部分请求会被持续发到目标或源的错误位置,引发重定向循环;若每次 TRYAGAIN(稍后重试)立即重试 10 次,瞬时请求可放大到 50,000 QPS(每秒查询率)。正确客户端只对 ASK(临时重定向)做一次带 ASKING(允许访问导入槽)的重试,并对可重试错误使用指数退避和总时限。
热门面试题
问题(基础题):MOVED(永久重定向)和 ASK(临时重定向)有什么区别?
- 考点:稳定槽位归属与迁移中的键级转发。
- 回答思路:说明是否更新长期槽位缓存。
- 详细答案:MOVED(永久重定向)表示目标槽的稳定归属已经在另一节点,客户端应刷新该槽的长期映射并重试。ASK(临时重定向)表示迁槽尚未完成,当前键可能已到目标节点,但槽整体仍归源节点;客户端仅本次向目标节点发送 ASKING(允许访问导入槽)后重试,不能据此改写长期映射。
- 进阶追问:为什么 ASK(临时重定向)前必须发送 ASKING(允许访问导入槽)?
- 进阶回答:导入节点需要区分正常路由错误与迁移转发请求;ASKING(允许访问导入槽)为紧随其后的单条命令授权访问导入中的槽,避免把未完成归属当作常规服务范围。
问题(原理题):CROSSSLOT(跨槽错误)为什么不能由 Cluster(集群)自动分布式执行?
- 考点:无跨分片事务协调器的设计边界。
- 回答思路:说明多主节点、网络故障与原子提交成本。
- 详细答案:跨槽键位于不同主节点,若任意多键命令都自动协调,就需要分布式锁、提交协议、故障恢复和死锁处理,会显著改变 Redis(远程字典服务)的延迟与复杂度模型。Cluster(集群)明确把原子多键操作限制在同槽,迫使业务在键建模阶段说明聚合边界;跨槽业务则通过权威事务、状态机或补偿处理。
- 进阶追问:把键复制到同槽镜像能解决吗?
- 进阶回答:镜像引入双写、乱序和失效问题,只适合可接受最终一致的读模型。不能把镜像当成跨槽资金或库存事务的正确性基础。
问题(项目追问题):迁槽期间支付查询出现重定向错误如何处理?
- 考点:客户端能力、重试边界与权威回源。
- 回答思路:区分读模型失败和支付副作用。
- 详细答案:使用支持 Cluster(集群)的客户端自动处理 MOVED(永久重定向)和 ASK(临时重定向),并设置小而有限的总重试预算。订单查询缓存失败时可按订单版本回源权威库;支付发起、退款等副作用不能在重定向超时时盲目重发,而是以业务请求号查询当前状态。迁移前先灰度客户端、观察重定向率与尾延迟,迁移中限制批量查询和大键操作。
- 进阶追问:客户端槽位缓存多久刷新一次最好?
- 进阶回答:不能只依赖固定轮询,主要通过 MOVED(永久重定向)即时修正,并以周期性拓扑刷新兜底。频率要平衡控制面开销与拓扑变更响应,迁移窗口还需观测实际重定向比例。
6. Cluster(集群)控制平面与在线扩缩容
6.1 Gossip(流言传播)、配置纪元与故障检测
Cluster(集群)节点之间通过 Cluster Bus(集群总线)交换 PING(探测消息)、PONG(响应消息)、MEET(握手消息)和故障信息;这是一种 Gossip(流言传播)式的去中心化成员信息传播。节点本地发现对端超时后先标为 PFAIL(疑似故障),再把怀疑传播出去;当收到足够其他主节点关于同一节点的故障报告时,形成 FAIL(确认故障)。配置纪元用于解决槽位归属冲突:更高纪元的声明可覆盖更旧的声明,从而让全体节点最终收敛到同一槽位所有者。它不是强一致协议,传播和收敛都需要时间。
flowchart LR
A["主节点 A 发现 B 超时"] --> P["PFAIL(疑似故障)"]
P --> G["Gossip(流言传播)携带故障报告"]
G --> C["其他主节点独立探测 B"]
C --> F{"报告达到故障确认条件?"}
F -->|否| W["继续传播与等待"]
F -->|是| X["FAIL(确认故障)"]
X --> E["B 的从节点发起故障转移"]| 控制面概念 | 作用 | 不是 | 运维关注点 |
|---|---|---|---|
| Gossip(流言传播) | 分散传播节点、槽位与故障视图 | 即时全局一致广播 | 总线连通性、传播延迟 |
| PFAIL(疑似故障) | 单节点本地超时怀疑 | 可以立即提升副本的结论 | 单节点网络、暂停、负载 |
| FAIL(确认故障) | 多主节点证据后确认失效 | 数据已经完整恢复 | 副本候选和切换耗时 |
| 配置纪元 | 解决槽位归属更新冲突 | 业务版本或事务编号 | 纪元单调性、陈旧节点恢复 |
数据演绎 9:故障检测不是瞬时的。 cluster-node-timeout(集群节点超时)设为 5 秒。节点 A 在第 5 秒将 B 标为 PFAIL(疑似故障),随后故障报告经 Cluster Bus(集群总线)传播,其他主节点还要独立超时与交换视图;即使 B 的从节点很快被提升,客户端更新槽位缓存和重连仍需额外时间。因此业务的故障预算不能写成“5 秒自动恢复”,而应按检测、选举、提升、传播、客户端重试和热身分别测量 P95(95 分位响应时间)与最坏值。
热门面试题
问题(基础题):Cluster(集群)的 Gossip(流言传播)主要传播什么?
- 考点:成员、槽位、健康与故障信息的去中心化同步。
- 回答思路:说明节点不依赖单一中心协调者。
- 详细答案:Cluster(集群)节点在 Cluster Bus(集群总线)消息中交换已知节点、地址、槽位归属、复制关系、配置纪元、心跳时间和故障报告。消息不会要求每次都携带完整全量状态,而是通过持续交换让信息逐步扩散,因此拓扑可在节点加入、槽迁移和故障时最终收敛,避免单一协调节点成为瓶颈。
- 进阶追问:Gossip(流言传播)为什么会有短暂视图不一致?
- 进阶回答:消息经网络、事件循环和随机节点选择传播,分区或负载会延迟部分节点收到更新;客户端也有本地槽位缓存。因此必须支持 MOVED(永久重定向)、ASK(临时重定向)和有限重试,而不能假设拓扑在同一时刻全局一致。
问题(原理题):PFAIL(疑似故障)和 FAIL(确认故障)为什么要分层?
- 考点:局部误判抑制与多主节点确认。
- 回答思路:从单节点网络故障和真正节点失效区分。
- 详细答案:一个节点看不到对端,原因可能是自身暂停、局部网络、对端过载或对端宕机。若单个观察者即可触发提升,就会在短暂抖动中造成频繁错误转移。PFAIL(疑似故障)保留本地怀疑并传播;其他主节点也观察到异常后才形成 FAIL(确认故障),在恢复速度和误判成本之间取得平衡。
- 进阶追问:所有节点都要同意 FAIL(确认故障)吗?
- 进阶回答:不需要等待全体,否则少数故障或隔离节点会阻塞恢复。确认依赖足够的主节点故障报告和集群规则,随后新拓扑继续通过 Gossip(流言传播)扩散;这也意味着分区下仍必须承认可用性与一致性的取舍。
问题(项目追问题):如何排查 Cluster(集群)偶发错误切换?
- 考点:控制面证据链而非只看业务报错。
- 回答思路:按节点超时、总线网络、资源停顿和纪元传播排查。
- 详细答案:先从节点日志提取 PFAIL(疑似故障)、FAIL(确认故障)、选举与纪元变化时间线,再对齐 Cluster Bus(集群总线)连接、丢包、网络往返、主机 CPU(中央处理器)争用、长时间阻塞命令、持久化 fork(创建子进程)停顿和系统暂停。若只有部分节点报告,优先查局部网络;若全体同时延迟,查资源饱和。修复后通过故障注入验证阈值、总线隔离和客户端重连。
- 进阶追问:能直接调大
cluster-node-timeout(集群节点超时)吗? - 进阶回答:调大会降低误判,却拉长真实故障的不可用时间。应先定位异常延迟来源,按正常高分位和业务恢复目标设阈值,并保留容量余量;不能用大超时掩盖阻塞命令或网络问题。
6.2 迁槽、扩缩容与再平衡的安全步骤
扩容不是把新节点加入就自动搬走所有数据,而是先加入节点并建立主从关系,再把一部分 slot(槽)标记为源节点 MIGRATING(迁出中)和目标节点 IMPORTING(导入中),按键逐步迁移;键完成迁移后才更新槽归属并传播。缩容要反向先迁空待下线主节点的全部槽位,再让其离开集群。在线迁移会占用网络、处理器和磁盘,并增加 ASK(临时重定向)及尾延迟;迁移计划必须避开热点、限速、可暂停,并在业务低峰执行。可复用已有的 redis-cluster-slots.puml 查看槽位、迁移和故障转移关系。
flowchart TD
J["新增节点并验证 Cluster Bus(集群总线)"] --> P["规划要移动的 slot(槽)范围"]
P --> M["源:MIGRATING(迁出中)\n目标:IMPORTING(导入中)"]
M --> K["按键复制并校验数量"]
K --> R["处理 ASK(临时重定向)窗口"]
R --> O["提交槽位新归属并传播纪元"]
O --> V["校验覆盖率、延迟与副本健康"]
V --> N["下一批或暂停回退"]| 阶段 | 必做检查 | 风险信号 | 安全动作 |
|---|---|---|---|
| 扩容前 | 新节点资源、版本、时钟与网络 | 资源水位已高、总线不通 | 先修基础设施,不启动迁移 |
| 规划槽位 | 键数、字节、热度与副本分布 | 只按槽数均分、忽略热键 | 分批且优先搬低风险槽 |
| 迁移中 | ASK(临时重定向)率、命令延迟、带宽 | 重试飙升、复制延迟扩大 | 限速、暂停、缩小批次 |
| 迁移后 | 槽覆盖、键抽样、读写错误率 | 残留槽、空槽或异常归属 | 修复归属再进入下一批 |
| 缩容 | 待下线主节点槽位是否清零 | 仍持有槽或副本缺失 | 不要先停节点,先清空槽 |
数据演绎 10:只按槽数迁移会拖垮尾延迟。 新增两个主节点后计划从旧节点各搬 1,000 个 slot(槽)。其中一个槽含 120 GiB(吉字节)轨迹索引和 40,000 QPS(每秒查询率)热键,另一个 300 个槽合计只有 8 GiB(吉字节)。按槽数平均会把最重槽与高峰流量同时迁移,ASK(临时重定向)和网络复制叠加导致 P99(99 分位响应时间)从 8 毫秒升至 300 毫秒。正确计划应按键数、字节和 QPS(每秒查询率)加权,先搬冷槽,再对重槽做拆键或低峰限速。
热门面试题
问题(基础题):Cluster(集群)扩容的正确顺序是什么?
- 考点:加入节点、迁槽、校验与副本保护。
- 回答思路:说明“加入”不等于“数据已经均衡”。
- 详细答案:先验证新节点版本、网络、内存与 Cluster Bus(集群总线)连通,再加入集群并规划主从拓扑;随后按批次把源槽置为 MIGRATING(迁出中)、目标槽置为 IMPORTING(导入中),迁移键并提交槽归属,持续检查延迟、重定向和副本健康。所有批次完成后还要验证槽覆盖和流量均衡。不能先停止旧节点或只看到新节点加入就宣布扩容完成。
- 进阶追问:为什么建议分批迁移?
- 进阶回答:迁移与线上请求竞争网络和处理器,且重试会放大尾延迟。分批可以在指标恶化时暂停,隔离问题槽,并把影响限制在可观察、可回退的范围。
问题(原理题):缩容为什么必须先清空槽位?
- 考点:槽归属仍是路由真相。
- 回答思路:说明客户端仍会把该槽请求发往旧主。
- 详细答案:只要一个主节点仍拥有 slot(槽),集群元数据和客户端路由就会把对应键请求发送给它。直接下线会造成槽未覆盖、请求失败或频繁重定向。缩容必须先把待下线主节点的全部槽和数据迁到其他主节点,确认它不再承担槽位且副本关系已恢复,再移除节点。
- 进阶追问:从节点缩容是否更简单?
- 进阶回答:它不持有槽但仍影响副本冗余。移除前要确认每个主节点仍满足副本数、故障域分散和复制延迟目标,避免把容量操作变成高可用降级。
问题(项目追问题):如何为跨境物流大促做 Redis(远程字典服务)扩容演练?
- 考点:容量、热度、客户端与回滚的全链路验收。
- 回答思路:以真实访问分布和故障预算验证。
- 详细答案:先按运单、承运商、租户和键前缀统计字节、QPS(每秒查询率)、热键与大键,而不是只看键总数;在预生产回放流量,验证新增节点、迁槽限速、ASK(临时重定向)处理和客户端槽位刷新。演练必须注入迁移暂停、节点故障和网络抖动,记录 P99(99 分位响应时间)、错误率、复制延迟、回源峰值与恢复时间。正式操作按冷槽优先、灰度批次、可暂停与明确回退条件执行。
- 进阶追问:迁移期间缓存全失怎么办?
- 进阶回答:以数据库和事件库为权威源,按统一回源闸门、请求合并和优先级逐步重建;支付、库存等关键决策始终走权威校验,不能为追求缓存恢复速度放开错误写入。
7. 分片故障转移、线上排障与项目话术
7.1 Cluster(集群)故障转移与可观测排障
某个 Cluster(集群)主节点进入 FAIL(确认故障)后,其从节点会根据复制偏移量、主节点故障状态和配置纪元竞争提升。获胜者把自己提升为新主、接管对应 slot(槽),并通过 Gossip(流言传播)发布更高纪元;客户端随后经 MOVED(永久重定向)或拓扑刷新访问新主。此过程保证的是分片服务尽可能恢复,不保证旧主最后返回的写全部存在。线上排障要把症状拆为路由、复制、控制面、数据面与业务面:错误率可能来自槽未覆盖,慢请求可能来自迁移或大键,业务不一致可能来自把缓存状态误当事实。
flowchart LR
A["现象:错误率或延迟升高"] --> B{"是否为 MOVED(永久重定向)/ASK(临时重定向)?"}
B -->|是| C["查槽位归属、客户端版本、迁槽批次"]
B -->|否| D{"是否有 FAIL(确认故障)或选举?"}
D -->|是| E["查副本偏移量、纪元、切换耗时与旧主围栏"]
D -->|否| F["查慢命令、大键、网络、内存与复制延迟"]
C --> G["核对权威数据与业务版本"]
E --> G
F --> G| 现象 | 优先证据 | 可能根因 | 首要处置 |
|---|---|---|---|
| 大量 MOVED(永久重定向) | 客户端重定向率、槽位版本 | 客户端过旧、迁槽或拓扑缓存失效 | 升级或刷新客户端,控制迁移批次 |
| ASK(临时重定向)激增 | 迁移进度、带宽、P99(99 分位响应时间) | 迁移过快、热点槽迁移 | 限速或暂停,优先迁冷槽 |
| 主分片切换 | FAIL(确认故障)时间线、偏移量 | 节点故障、网络隔离、误判 | 围栏旧主,核对副本与业务回退 |
| 槽未覆盖 | 槽位覆盖检查、节点列表 | 节点被误下线或迁移中断 | 修复槽归属,避免盲目重启 |
| 数据看似回退 | 权威版本、缓存版本、复制偏移量 | 异步复制丢最近写、旧缓存回填 | 以权威事实修复并评估影响 |
数据演绎 11:把故障恢复时间拆开测。 一次主分片故障中,检测用了 6 秒,副本选举和提升用了 2 秒,Gossip(流言传播)收敛用了 1 秒,客户端连接池发现旧连接失效并刷新拓扑用了 4 秒,总业务可见影响约 13 秒。若只把 cluster-node-timeout(集群节点超时)从 6 秒调到 3 秒,可能把影响降到 10 秒,却增加误判;更有效的改进可能是客户端快速失败和重连把 4 秒降到 0.5 秒,或提高副本资源避免提升耗时。每一段都应有指标,不能只报一个“自动切换秒数”。
热门面试题
问题(基础题):Cluster(集群)主分片故障后发生了什么?
- 考点:故障确认、从节点选举、槽位接管与客户端恢复。
- 回答思路:按控制面到数据面顺序描述。
- 详细答案:其他主节点经心跳和 Gossip(流言传播)形成对故障主节点的 FAIL(确认故障)判断;其从节点竞争提升,获胜节点成为新主并接管原槽位,以更高配置纪元传播新归属。客户端的旧连接会失败,支持 Cluster(集群)的客户端通过拓扑刷新或 MOVED(永久重定向)找到新主。该链路恢复可用写入口,但可能丢失旧主最后尚未复制的写。
- 进阶追问:为什么新主不一定是复制偏移量最大的唯一节点?
- 进阶回答:选举还受可达性、故障状态、配置纪元和投票影响;复制偏移量是候选质量的重要因素但不是全局提交证明。设计必须接受并处理复制回退窗口。
问题(原理题):如何区分 Cluster(集群)故障与客户端路由故障?
- 考点:服务端控制面和客户端拓扑缓存的独立证据。
- 回答思路:同时看节点健康、槽覆盖和客户端错误分布。
- 详细答案:若节点视图健康、槽位完整覆盖、服务端命令延迟正常,但只有旧版本应用大量出现 MOVED(永久重定向)或连接旧地址,优先怀疑客户端拓扑缓存、连接池或地址发现。若多个客户端同时报槽未覆盖、FAIL(确认故障)或复制断链,则检查集群控制面和节点资源。排障必须收集两端日志与时间线,不能看到重定向就直接重启节点。
- 进阶追问:为什么重启节点通常不是第一动作?
- 进阶回答:重启可能清除内存现场、放大副本重同步和迁移压力,并让短暂网络问题变成真实主故障。先限制流量、保存拓扑与日志、确认槽归属和故障域,才能选择针对性操作。
问题(项目追问题):请用两分钟讲一个 Redis(远程字典服务)集群故障处理案例。
- 考点:业务影响、证据、止损、根因、修复与预防。
- 回答思路:按时间线串联 WMS(仓储管理系统)读模型边界。
- 详细答案:我会说明大促期间一个承载库存展示的主分片出现网络隔离,先看到客户端 MOVED(永久重定向)和超时上升,数据库回源开始接近安全水位。我们立即开启按接口分级的回源闸门,库存扣减仍走数据库条件更新,列表查询返回带更新时间的旧值。随后从 Cluster(集群)日志确认 FAIL(确认故障)、副本提升和客户端刷新各自耗时,定位是交换机端口抖动叠加旧客户端刷新慢。恢复后按权威版本重建缓存、核对库存守恒,再升级客户端、拆分热点键并演练网络隔离。
- 进阶追问:如何证明没有超卖?
- 进阶回答:用库存流水和数据库条件更新结果核对期初、入库、出库、冻结与可用量守恒;缓存仅用于展示和预判,故障期间任何缓存回退都不改变权威扣减结果。
8. 端到端正确性边界图
flowchart TB
U["用户请求"] --> A["应用幂等与参数校验"]
A --> D["权威数据库状态机、唯一约束与流水"]
D --> E["事务外盒或事件流"]
E --> R["Redis(远程字典服务)读模型"]
R --> H["主从、Sentinel(哨兵)或 Cluster(集群)"]
H --> Q["副本与故障转移降低可用性风险"]
D --> O["对账、补偿与可重建"]
Q --> O9. 综合题库使用说明
本节是非知识型过渡,不添加 kb:knowledge 标记。下面 24 道题用于模拟高级面试中的连续追问;每题都应先独立口述,再检查是否说明了确认点、故障窗口、权威事实、监控证据和补偿路径。
10. 高频综合面试题与追问
问题:请完整比较单机、主从、Sentinel(哨兵)与 Cluster(集群),并给出你的选型顺序。
- 口述答案:我不会从“哪个功能最多”开始选,而是先把数据角色、容量、故障目标和访问模型说清。单机适合可丢、可重建、短暂不可用可接受的缓存,优点是链路短,缺点是机器或进程故障即中断。主从在一个主节点后增加异步副本,能分摊部分读请求并提供恢复来源,但主节点失效仍需人工切换,复制延迟也会造成从读旧值。Sentinel(哨兵)在单主容量足够时增加故障检测、领导者选举、提升从节点和客户端地址发现,适合需要自动切换但不需要分片的读模型。Cluster(集群)把键映射到 16384 个 slot(槽),多个主节点分别承担槽位并各有副本,适合容量、吞吐或恢复时间已经超过单机边界的场景;代价是跨槽限制、迁槽、客户端路由和更复杂的运维。我的选型顺序是先让正确性独立于缓存,再评估单主是否能承载热集和恢复目标;能承载时优先 Sentinel(哨兵)而不是过早分片,不能承载时才按键聚合设计 Cluster(集群)。WMS(仓储管理系统)库存的最终扣减始终走数据库条件更新,Redis(远程字典服务)拓扑只影响读加速与削峰,不决定是否超卖。
- 进阶追问与回答:什么时候宁可不用 Sentinel(哨兵)?若缓存全失可立即降级、重建时间短且业务允许人工恢复,单机更简单;但必须明确监控、备份和恢复手册,不能把“暂时不做高可用”误写成“不会出故障”。
问题:主从复制的确认点有哪些?为什么客户端收到成功仍可能丢数据?
- 口述答案:一次写从客户端发到主节点开始,至少经历主节点命令执行、客户端响应、复制流写入从节点连接、从节点应用到内存、从节点回报复制偏移量、主从各自持久化等多个阶段。默认异步复制中,主节点通常在本地内存执行成功后就返回,所以这个成功最多证明旧主当时已接受该命令,不证明任何副本已经收到,更不证明副本稳定落盘或未来一定成为新主。主节点在复制前崩溃,最后写直接消失;主节点复制后崩溃而新主选到落后副本,也会回退。WAIT(等待副本确认)可以等待指定数量副本确认复制偏移量,缩小写只存在于单主的窗口,但它不要求副本落盘,也不锁定选主结果;网络分区、多个节点同时故障仍可让写丢失。工程上应按数据角色分级:会话、计数和可重建列表可以接受窗口;支付状态、库存流水和执行结果要落在具备事务、唯一约束、审计与对账能力的权威存储。缓存只保存读模型或短期幂等窗口,发生切换后能从权威状态和事件流重建。监控还要同时看主从偏移量差、复制延迟、WAIT(等待副本确认)超时与故障转移后的版本回退,不能只看“副本在线”。
- 进阶追问与回答:如果 WAIT(等待副本确认)超时,能否直接重发?不能。写可能已在主节点执行,甚至稍后被副本收到;必须按业务请求号查询权威状态,只有确认未生效才安全重试,避免支付或任务产生重复副作用。
问题:请解释 Sentinel(哨兵)的主观下线、客观下线、quorum(法定票数)和领导者选举。
- 口述答案:Sentinel(哨兵)持续监控主节点、从节点和其他 Sentinel(哨兵)。当某一个 Sentinel(哨兵)在配置超时内得不到主节点有效响应,它先形成 SDOWN(主观下线);这只是本地视角,可能是自身暂停、局部网络故障或对端真正宕机,因此不能据此立刻切主。它会向同行询问对该主节点的判断,当获得不少于 quorum(法定票数)的同意时形成 ODOWN(客观下线)。quorum(法定票数)只解决“当前观察者是否有足够证据认为主不可达”,不等于谁能执行转移。随后 Sentinel(哨兵)进入新的配置纪元请求领导者授权,每个 Sentinel(哨兵)对同一纪元通常只投一票,获得多数授权的候选成为领导者,负责挑选副本、提升新主、重配其他副本并通知客户端。把发现阈值和多数选举分开,既避免一个局部故障马上切换,又避免多个观察者同时提升不同从节点。生产通常部署奇数个、跨故障域的 Sentinel(哨兵),例如三个实例;只有两个实例时任一边隔离都难以获得多数。配置时还要结合真实网络高分位和进程暂停设置超时,过短会误切换,过长会拉长业务不可用。
- 进阶追问与回答:quorum(法定票数)设成 1 是否更快?表面更快,实际会让单个 Sentinel(哨兵)的网络抖动触发 ODOWN(客观下线)候选,增加抖动和错误转移;恢复速度必须和误判成本一起评估。
问题:Sentinel(哨兵)故障转移的完整过程,以及它为什么仍可能丢最后写?
- 口述答案:在主节点形成 ODOWN(客观下线)并选出领导者后,领导者先从可用从节点中挑选候选。候选判断会考虑副本优先级、与主节点断连时间、复制健康状况和复制偏移量,偏移量更大通常意味着包含更多原主已经执行的数据。领导者命令候选停止复制并提升为新主,再让其他从节点改为复制新主,更新监控配置并向客户端发布新的主地址;旧主恢复后会被降级为新主的从节点,防止长期双主。这个流程恢复的是写入口和复制拓扑,不是对所有客户端成功写的共识提交。若最后两条命令刚在旧主执行并返回,但尚未进入任何副本,最好的候选也无法恢复;若副本已经收到而它随后不可用,或选举时只能选到另一个较落后的副本,同样会回退。因而不能只用“自动切换”证明数据安全。对支付、库存和 Runner(执行器)任务,权威数据库中的状态机、业务唯一键、流水与补偿才是最终事实;Redis(远程字典服务)切换后要通过版本、事件位点和对账恢复读模型。排障时我会记录主故障时间、各副本偏移量、被选副本、客户端重连耗时与回退的业务版本,量化而不是猜测丢失窗口。
- 进阶追问与回答:为什么不总是选择最新副本并保证不丢?“最新”只是在已存活副本中偏移量较大,无法包含尚未复制的写,也无法保证该副本同时具备可达性、优先级和成功提升条件。
问题:什么是 Sentinel(哨兵)脑裂?如何从客户端、网络和业务三层防护?
- 口述答案:脑裂的典型时序是旧主与 Sentinel(哨兵)多数失联,但仍和一部分客户端连通;多数区认为旧主不可达,提升从节点成为新主,而隔离区客户端因为硬编码地址、连接未断或刷新滞后,继续向旧主写。于是两个节点各自产生写历史。网络恢复后,旧主会被重新配置为新主的从节点,并以新主历史为准追赶;隔离期间仅存在于旧主的写通常会被覆盖或丢弃。客户端层必须使用 Sentinel(哨兵)发现机制、识别连接错误并及时刷新主地址,不能在配置中永久固定旧主 IP(网络地址);但这只能缩短窗口,不能保证已有连接立即停止。网络层需要把旧主从负载均衡和写流量中摘除,必要时进行主机或网络围栏,避免孤岛继续服务。服务端层可用
min-replicas-to-write(最少副本写入数)在健康副本不足时拒绝新写,减少孤立主积累确认写。业务层最关键:缓存状态不能是资金、库存和任务副作用的唯一事实,必须用数据库唯一键、状态机、fencing token(栅栏令牌)或版本检查拒绝旧执行者。脑裂演练应模拟单向网络隔离、旧连接存活和旧主恢复,验收的是无重复副作用、可对账和可重建,而不是只看是否完成了切主。 - 进阶追问与回答:
min-replicas-to-write(最少副本写入数)能杜绝脑裂吗?不能。它依赖主节点对副本健康的观测,只能在副本数量或延迟不达标时拒写;网络视图和已返回写的窗口仍不是共识保证。
- 口述答案:脑裂的典型时序是旧主与 Sentinel(哨兵)多数失联,但仍和一部分客户端连通;多数区认为旧主不可达,提升从节点成为新主,而隔离区客户端因为硬编码地址、连接未断或刷新滞后,继续向旧主写。于是两个节点各自产生写历史。网络恢复后,旧主会被重新配置为新主的从节点,并以新主历史为准追赶;隔离期间仅存在于旧主的写通常会被覆盖或丢弃。客户端层必须使用 Sentinel(哨兵)发现机制、识别连接错误并及时刷新主地址,不能在配置中永久固定旧主 IP(网络地址);但这只能缩短窗口,不能保证已有连接立即停止。网络层需要把旧主从负载均衡和写流量中摘除,必要时进行主机或网络围栏,避免孤岛继续服务。服务端层可用
问题:请说明
min-replicas-to-write(最少副本写入数)与 WAIT(等待副本确认)的差异和组合方式。- 口述答案:两者都围绕副本,但作用时机不同。
min-replicas-to-write(最少副本写入数)是主节点的准入保护:只有当至少指定数量的从节点、且复制延迟不超过min-replicas-max-lag(最少副本最大延迟)时,主节点才接受新的写。它避免主节点在没有健康冗余时持续产生只存在于自身的成功写,代价是在副本短暂抖动时直接拒写。WAIT(等待副本确认)是客户端在当前写之后主动等待副本确认某个复制偏移量;它能让单次关键写在返回前观察到更多副本已追上,代价是增加尾延迟和超时处理复杂度。组合方式是先用最少副本配置建立全局拒写防线,再对少量可承受额外延迟的状态写使用 WAIT(等待副本确认);例如订单查询读模型可以只依赖前者或完全异步,短期操作令牌可在写后等待一个副本,而支付扣款事实仍只由权威数据库提交。无论哪种组合,应用都必须用业务幂等键处理超时,因为“等待超时”不是“写失败”。配置参数应基于副本延迟分位、故障域、可接受拒写时长和实际演练推导;把阈值设得极小会让短暂网络抖动变成全站写不可用。 - 进阶追问与回答:如果写被最少副本规则拒绝,是否应该无限重试?不应该。应有限退避,并按数据类型选择排队、回源权威库、返回稍后重试或允许陈旧读;无限重试会在副本故障期间放大负载。
- 口述答案:两者都围绕副本,但作用时机不同。
问题:Cluster(集群)为什么使用 16384 个 slot(槽),客户端如何定位键?
- 口述答案:Cluster(集群)在键和节点之间引入固定的 16384 个 slot(槽)。客户端对键名计算 CRC16(循环冗余校验 16 位),再对 16384 取模,得到唯一槽号;集群元数据记录每个槽归哪个主节点,客户端缓存这张映射后直接访问目标节点。固定槽位的价值是扩缩容时迁移的是明确槽范围,而不是改动所有键的哈希规则:把一批槽和其中的键从旧主迁到新主,再发布新归属即可。16384 是粒度和元数据传播成本之间的工程权衡,不是节点数量或键数量的上限;一个节点可持有不连续槽,一个槽也可包含大量键。路由设计还必须承认客户端缓存会陈旧,所以节点通过 MOVED(永久重定向)通知稳定的新归属,客户端据此刷新;迁移窗口则用 ASK(临时重定向)处理。选 Cluster(集群)前要先审计跨键操作:同一业务聚合若必须原子访问,应通过 hash tag(哈希标签)让最小集合落在同槽;跨槽业务不能指望集群自动完成分布式事务,应在权威系统用状态机或补偿编排。容量规划不仅看槽均衡,也要看每槽键字节、QPS(每秒查询率)和热键,因为一个极热键仍然只能驻留于一个槽和一个主节点。
- 进阶追问与回答:为什么不把槽数设得更大?槽位越多,元数据、消息与迁移管理的开销越高;实际节点规模远小于槽数,16384 已提供足够的迁移粒度,关键是正确处理热点而非无限细分元数据。
问题:hash tag(哈希标签)如何使用?它解决什么,又会引入什么风险?
- 口述答案:hash tag(哈希标签)是键名中的首对非空花括号内容。Cluster(集群)计算槽位时只对这段内容做 CRC16(循环冗余校验 16 位),因此
{order:42}:header、{order:42}:items和{order:42}:version会落到同一 slot(槽)。它解决的是同一最小业务聚合需要多键原子读取、Lua(脚本语言)处理或事务操作时的路由约束,让这些键不会触发 CROSSSLOT(跨槽错误)。但标签不是业务事务魔法:它只把键放在同一主节点,无法保证数据库与缓存、多个聚合或外部服务的一致提交。标签选得太粗还会制造热点,例如把整个仓库、整个租户甚至所有键写成同一标签,会让分片退化为单节点瓶颈;选得太细则无法满足同槽命令。我的做法是先画出真正需要原子协作的键集合,把仓库、货主、SKU(库存单位)等最小库存聚合作为标签,其他列表、索引和统计键独立分散。上线前用真实键样本计算槽位分布和每标签 QPS(每秒查询率),并对大促场景做热点压测。跨仓调拨、支付与库存联动等跨聚合流程仍以数据库状态机、事件和补偿为主,缓存只更新各自读模型。 - 进阶追问与回答:可以通过多个标签让同一键同时落多个槽吗?不可以,一个键只有一个槽位计算结果。若需要复制给不同分片,必须显式维护多份读模型,并处理双写、乱序和最终一致边界。
- 口述答案:hash tag(哈希标签)是键名中的首对非空花括号内容。Cluster(集群)计算槽位时只对这段内容做 CRC16(循环冗余校验 16 位),因此
问题:请用时序解释 MOVED(永久重定向)与 ASK(临时重定向),客户端为什么不能混用?
- 口述答案:MOVED(永久重定向)说明一个 slot(槽)的稳定归属已经改变:客户端把请求发给旧节点,旧节点返回目标节点地址和槽号,客户端应更新本地槽位缓存并向目标重试,后续该槽请求直接走新节点。ASK(临时重定向)发生在迁槽过程中:源节点把槽标为 MIGRATING(迁出中),目标节点标为 IMPORTING(导入中),某个键已经迁到目标而整个槽的最终归属尚未提交。源节点返回 ASK(临时重定向)后,客户端只对本次请求连接目标,先发送 ASKING(允许访问导入槽),再重发原命令;它不能把目标永久写入槽位缓存,因为同槽其他键或后续请求仍可能由源节点负责。混用会造成持续访问错误节点、重定向循环和额外尾延迟。成熟客户端应自动识别两类响应,对 ASK(临时重定向)做一次带 ASKING(允许访问导入槽)的重试,对 MOVED(永久重定向)刷新缓存,对 TRYAGAIN(稍后重试)执行有上限的退避,并设总超时。业务层仍需把重定向失败视为缓存或读模型失败:支付和库存副作用按业务请求号查权威状态,不因网络错误盲目再执行;可陈旧的轨迹列表可以降级读旧值或回源。
- 进阶追问与回答:为什么 ASK(临时重定向)需要 ASKING(允许访问导入槽)?导入节点需要区分迁移转发和普通错误路由;该命令为紧随其后的单次访问授权,防止未完成的槽位归属被当作正常服务范围。
问题:Cluster(集群)中的 Gossip(流言传播)和 PFAIL(疑似故障)/FAIL(确认故障)如何工作?
- 口述答案:Cluster(集群)没有一个中心节点统一维护所有成员信息。节点通过 Cluster Bus(集群总线)交换 PING(探测消息)、PONG(响应消息)、MEET(握手消息)以及节点、槽位、配置纪元和故障报告,让拓扑信息以 Gossip(流言传播)方式逐步扩散。一个节点在
cluster-node-timeout(集群节点超时)内持续收不到对端有效响应时,先在本地标为 PFAIL(疑似故障);这和 Sentinel(哨兵)的主观下线一样,只代表单点观察,可能是自身负载、局部网络或对端故障。它会把怀疑带给其他主节点,多个独立主节点也报告同一目标故障后,集群形成 FAIL(确认故障),对应分片的从节点才可能参与提升。分层的目的,是避免一次局部抖动立即触发错误切主,同时又不等待所有节点同意而无限延迟恢复。因为传播和选举需要时间,业务故障预算必须拆成检测、证据收敛、提升、槽位传播、客户端刷新与连接池重连;不能把超时参数当成总恢复时间。排障时我会对齐 PFAIL(疑似故障)日志、总线连接、网络丢包、主机 CPU(中央处理器)饱和、阻塞命令和持久化停顿,判断是真故障还是误判。 - 进阶追问与回答:把
cluster-node-timeout(集群节点超时)调大能避免误切换吗?能降低短抖动误判,却会延长真实故障不可用。正确做法是先治理网络、资源和阻塞,再按延迟分位与恢复目标设定阈值。
- 问题:如何设计一次安全的 Cluster(集群)扩容和迁槽操作?
- 口述答案:扩容前我先做容量和热度盘点:按 slot(槽)统计键数、内存字节、QPS(每秒查询率)、大键、热键、复制延迟和副本故障域,不能只看总键数或平均槽数。新节点加入前验证版本兼容、内存余量、网络、Cluster Bus(集群总线)端口、时间同步和监控;随后建立合理主从关系,避免新主没有副本。迁移计划按字节和流量加权,把冷槽、小槽优先分批移动,源节点标为 MIGRATING(迁出中)、目标标为 IMPORTING(导入中),迁移期间观察 ASK(临时重定向)率、客户端重试、P99(99 分位响应时间)、带宽、复制积压和数据库回源。每批都有暂停条件,例如尾延迟超过预算、重定向错误持续增长或副本延迟失控时立即限速或停止,而不是硬推完成。每批结束后验证槽覆盖、键抽样、读写成功率和副本健康,再进入下一批。缩容反向操作:先迁空待下线主节点的全部槽,再确认它不承担任何路由和副本责任后移除。整个过程要准备回退策略、变更窗口、客户端版本检查和业务降级;支付、库存等关键接口在缓存错误时仍按权威状态机运行,不能把迁槽问题放大成数据正确性事故。
- 进阶追问与回答:为什么平均分槽后仍可能不均衡?槽内数据大小和请求热度不同,单个 hot key(热键)也无法拆到多个槽。应按字节与 QPS(每秒查询率)再平衡,并对热点做多级缓存、请求合并或键模型调整。
- 问题:Cluster(集群)缩容或下线节点时,最容易犯什么错误?
- 口述答案:最危险的错误是看到节点资源紧张就直接停止主节点。只要它还拥有任何 slot(槽),客户端和集群元数据仍会路由相应键到它;突然下线会造成槽未覆盖、请求错误、频繁重定向甚至触发不必要故障转移。正确流程是先确认待下线节点的主从角色和槽位清单,规划目标节点的容量与热度,分批把全部槽和键迁出,迁移中处理 ASK(临时重定向)并监控复制健康,最终验证该节点槽数为零、集群覆盖完整、目标节点副本满足冗余要求,才允许移除或停止。第二个错误是只看主节点:移除从节点也会降低副本数和故障域隔离,可能使剩余主节点不满足
min-replicas-to-write(最少副本写入数)或在下一次故障中没有可提升候选。第三个错误是忽略客户端,一些旧客户端不支持拓扑刷新或把节点地址写死,缩容后会持续报连接错误。变更前要做客户端盘点和灰度,变更后核对 MOVED(永久重定向)率、槽覆盖、连接失败、延迟和业务版本。对可重建缓存可以限流回源,对库存和支付不允许因缓存下线绕过权威校验。 - 进阶追问与回答:迁槽完成后能否立刻删除旧节点数据目录?不能作为常规手段。应先保留必要日志和变更证据,确认节点已安全离集群、没有误连接与副本需求,再按运维流程清理;直接删除会让复盘和回退失去依据。
- 问题:Cluster(集群)主分片故障时,故障转移和 Sentinel(哨兵)有哪些相同与不同?
- 口述答案:两者的共同点是都以异步主从复制为数据冗余基础,都需要先判断主节点不可达,再从副本中选出新主并让客户端切换地址,因此都存在最后写尚未复制而丢失、旧客户端仍访问旧主和副本资源不足导致恢复失败的边界。不同点在控制面和数据模型。Sentinel(哨兵)面向一个单主复制组,独立 Sentinel(哨兵)进程负责监控、SDOWN(主观下线)、ODOWN(客观下线)、领导者选举和通知客户端;它不分片,全部键仍由一个主节点承载。Cluster(集群)内建多个主分片,每个主只拥有一部分 slot(槽),节点借助 Cluster Bus(集群总线)和 Gossip(流言传播)交换拓扑、故障与配置纪元,多个主节点对 PFAIL(疑似故障)和 FAIL(确认故障)形成证据,新主接管的是故障分片的槽位。Cluster(集群)需要额外处理客户端槽位缓存、MOVED(永久重定向)、ASK(临时重定向)和跨槽限制;Sentinel(哨兵)则主要处理单一主地址变更。选型上,容量仍在单主边界内且需要自动故障转移时,Sentinel(哨兵)通常更简单;需要横向拆分热集、缩短大实例恢复时间时才使用 Cluster(集群)。无论哪种,业务必须有独立权威事实与幂等,不能把“切换完成”当成“数据绝不回退”。
- 进阶追问与回答:能否把 Sentinel(哨兵)放在 Cluster(集群)外再监控所有主节点?这会混淆两套控制面,通常不用于 Cluster(集群)的分片故障转移;应使用集群自身机制并做好节点与客户端监控。
- 问题:一次 Cluster(集群)故障后发现缓存值回退,如何区分复制丢写、缓存旧值和业务事实错误?
- 口述答案:我先停止把“缓存回退”直接等同于数据库错误,而是建立按时间排序的证据链。第一层看控制面:故障主何时被标为 FAIL(确认故障),哪个副本被提升,旧主与各副本在故障前的复制偏移量差多少,是否存在
min-replicas-to-write(最少副本写入数)拒写或 WAIT(等待副本确认)超时。若新主偏移量确实小于旧主最后已执行位置,且缺失键只存在于这段窗口,属于异步复制回退。第二层看缓存模型:对比缓存值携带的业务版本、失效事件位点、过期时间和更新日志;若数据库已有更高版本但缓存仍是旧值,通常是删除失败、消息积压、乱序回填或从读陈旧,而非复制丢写。第三层看权威事实:查询数据库事务、库存流水、支付渠道回调和唯一业务号;若权威状态也错误或缺失,才进入业务事故处理和补偿。WMS(仓储管理系统)场景要用期初、入库、出库、冻结和可用量守恒核对;支付要用订单、渠道流水、退款和对账单核对。止损上,先让关键判断绕过缓存并限制回源,再根据根因重建读模型或回放补偿,绝不能为了让页面“看起来一致”直接覆盖权威数据。 - 进阶追问与回答:缓存没有版本字段怎么办?短期可通过事件时间、日志和权威数据抽样定位,长期必须补业务版本或单调位点;没有可比较版本就很难可靠拒绝乱序旧写。
- 问题:如何为支付订单查询设计 Redis(远程字典服务)高可用与一致性边界?
- 口述答案:支付设计的第一原则是订单、扣款、退款、账务和渠道回执必须在权威数据库或账务系统中形成可审计事实,Redis(远程字典服务)只能保存订单查询读模型、短期幂等窗口和限流状态。订单状态迁移用唯一业务号、合法状态机和事务写入;事务提交后通过事务外盒或事件流异步删除或版本化更新缓存。普通查询可以走 Cluster(集群)或 Sentinel(哨兵)副本体系,但响应要包含订单版本和更新时间;用户刚完成支付且携带已知更高版本时,应用发现缓存版本不足就读主库或主动查渠道,不能把旧的“处理中”当失败并再次扣款。缓存切换、迁槽或全失时,查询服务按订单和用户维度限流回源,必要时返回“处理中”及刷新时间;发起支付、退款和结算绝不依赖缓存命中。删除缓存失败时由可靠事件重试,迟到事件不能覆盖高版本缓存,后台按版本差抽样对账。可用性配置如 WAIT(等待副本确认)和
min-replicas-to-write(最少副本写入数)只能降低缓存状态丢失概率,不能替代资金正确性。演练要包括主从切换、网络分区、消息重复乱序和缓存全清,验收是同一请求号只产生一次副作用、金额守恒、状态不倒退且缓存可从权威事实重建。 - 进阶追问与回答:支付成功后缓存删除失败,能否回滚订单?不能。订单事务已经是事实,回滚会制造更大不一致;应可靠重试失效、版本回源和对账收敛。
- 问题:WMS(仓储管理系统)库存读模型在 Redis(远程字典服务)切换或脑裂时,怎样保证不超卖?
- 口述答案:我会把库存展示、预判和最终扣减严格拆开。Redis(远程字典服务)里可按最小库存聚合缓存可用量、冻结量和版本,用于列表展示、热点查询与早期拦截;如需要同槽 Lua(脚本语言)预扣,则把相关键使用同一个 hash tag(哈希标签),但该预扣仅是削峰闸门。最终扣减必须在数据库用
available >= quantity条件更新、库存流水和订单行唯一约束完成,任何缓存显示有货都不能跳过这条校验。网络分区导致 Sentinel(哨兵)脑裂或 Cluster(集群)新主回退时,缓存可能重复预扣、丢失预扣或显示旧库存,但数据库条件更新会拒绝超卖;成功或失败结果再通过事件修正读模型。对于缓存预扣成功但落库失败的情况,任务按业务号补偿释放;对于请求超时,先查订单行和流水而非再次扣减。缓存更新带库存版本,消费者用原子比较拒绝低版本,后台定期按期初加收入减出库减冻结等式核对。故障期间打开回源闸门、限制非核心列表和活动抢购入口,库存扣减仍可在数据库安全容量内服务。面试中我会强调高可用缓存提升的是体验和吞吐,防超卖的底线是权威条件更新、幂等和可审计流水。 - 进阶追问与回答:缓存预扣能否完全取消数据库扣减?不能。预扣状态在故障转移、过期、重启和补偿失败下不具备权威性;若以它为唯一库存,就会把缓存故障直接升级为账实错误。
- 问题:Runner(执行器)调度用 Redis(远程字典服务)锁,遇到主从切换为什么仍会重复执行?
- 口述答案:Redis(远程字典服务)锁通常依赖主节点写入一个带过期时间的令牌。默认异步复制下,执行器 A 在旧主成功获取锁后,锁写还没复制到副本,旧主故障并切到没有该锁的新主;执行器 B 在新主看到锁不存在,也成功获取锁,于是两者都开始执行。脑裂时更明显:隔离区的 A 和多数区的 B 可能各自从不同主节点获得锁。即使锁最终复制,进程暂停、网络延迟和租约过期也可能让 A 在不再持锁时继续操作下游资源。因此锁只能减少同时抢占,不能单独证明执行唯一性。可落地的做法是给每个任务生成持久化实例号,任务状态迁移使用数据库唯一约束或条件更新;锁返回的递增 fencing token(栅栏令牌)随请求传给下游,数据库或外部资源只接受比已处理版本更大的令牌。任务执行结果按业务幂等键落库,超时任务由扫描器重新领取,重复执行只会命中同一状态而不会产生二次副作用。故障转移时暂停非核心调度、收紧并发、等待新主和连接池稳定,再按持久化状态恢复。监控要包含锁获取率、续约失败、切换时间、重复领取、下游令牌拒绝和任务状态卡滞。
- 进阶追问与回答:延长锁过期时间能解决吗?只能降低短暂停顿的重复概率,却会延长真正宕机后的接管时间;锁时长与正确性不是等价关系,仍需幂等和围栏。
- 问题:为什么 Cluster(集群)不能天然解决 hot key(热键)问题?给出治理路径。
- 口述答案:Cluster(集群)能把不同键分布到多个 slot(槽)和主节点,却不能把一个键的读写拆给多个槽;无论集群扩到多少主节点,同一个 hot key(热键)都固定落在一个槽,最终压垮所在主节点的网络、单线程命令处理或输出缓冲。第一步是识别,不只看节点总 QPS(每秒查询率),还要按键、前缀、租户、命令类型统计请求量、响应大小、慢查询和连接等待。第二步按数据可变性治理:配置、活动规则等可短时间陈旧的值放入应用本地缓存并用版本或消息失效,100 个实例每秒各回源一次可把 50,000 QPS(每秒查询率)压到约 100 QPS(每秒查询率);可读副本分流时要接受复制延迟;大查询则分页、拆字段和限制返回大小。第三步若键代表可分割集合,可按用户、商品或时间桶拆成多个键,再由应用聚合;但不能对必须原子操作的单一计数器随意分片。第四步在活动前预热、请求合并、限流和降级,避免过期瞬间回源。扩容和迁槽只在热点已经可分散后有效。治理验收同时看热键所在节点 P99(99 分位响应时间)、输出缓冲、错误率、数据库回源和业务新鲜度,不能只看全局平均负载。
- 进阶追问与回答:能否用 hash tag(哈希标签)分散热键?不能。hash tag(哈希标签)只控制同槽,不能把同一键复制到多个槽;粗标签反而可能让更多相关键集中在热点节点。
- 问题:一次迁槽引起 P99(99 分位响应时间)飙升,你会如何排查和止损?
- 口述答案:我先确认是否与迁移时间高度相关:查看迁移批次、源与目标节点的网络带宽、CPU(中央处理器)、内存、复制延迟、客户端 ASK(临时重定向)率和重试次数,再和 P99(99 分位响应时间)、错误率、连接池等待、慢命令对齐。若 ASK(临时重定向)和带宽同时上升,通常是迁移复制与线上请求竞争;若源节点出现大键序列化或目标节点输出缓冲增长,则要查具体槽内的大键和热点。止损顺序是先暂停或限速当前批次,避免继续扩大影响;对非核心接口打开有界旧值、限流或回源保护,防止缓存慢导致数据库被重试打穿;保留迁移状态和日志,不能立即重启节点破坏现场。随后将计划由“按槽数量平均”改为按键字节和 QPS(每秒查询率)加权,冷槽优先,热点槽在低峰迁移或先拆键;客户端应保证对 ASK(临时重定向)只做一次 ASKING(允许访问导入槽)重试,对 TRYAGAIN(稍后重试)有限退避。恢复后做槽覆盖、键抽样、主从偏移量、错误率和权威版本核对。复盘输出每批可接受的延迟预算、暂停阈值、带宽上限和演练用例,避免下次只靠经验操作。
- 进阶追问与回答:为什么不把所有迁移都放在夜间?低峰能降低冲击,但夜间也可能有批处理、备份和跨境物流任务;仍需按真实资源曲线限速、监控并准备暂停。
- 问题:Redis(远程字典服务)Cluster(集群)里出现 CROSSSLOT(跨槽错误),如何判断该改键还是改业务流程?
- 口述答案:先识别这组键之间到底需要什么语义。如果它们只是同一页面的多个独立字段,应用可以分别读取并容忍短暂不一致,不应为了消除 CROSSSLOT(跨槽错误)把所有键强行放同一 hash tag(哈希标签)。如果它们代表同一最小聚合,确实需要在一条 Lua(脚本语言)脚本或事务中原子检查和更新,例如同一仓库、货主、SKU(库存单位)的可用量与冻结量,则应给这些键使用精确而窄的共同标签。若操作跨两个库存聚合、跨订单和库存、跨支付与账务,说明它本质上是分布式业务流程;把键塞进同槽只能临时绕过路由错误,却会造成热点和把错误的一致性责任交给缓存。正确做法是在权威数据库定义状态机、唯一键和可恢复步骤,通过事件驱动更新各分片读模型,并对失败进行补偿。判断标准是:失败时是否允许其中一个步骤先成功、是否需要审计与对账、是否涉及外部副作用、数据量是否会形成热点。只要涉及资金、库存跨仓、支付回调等,答案通常是改业务流程而不是改 hash tag(哈希标签)。最后用压测验证修改后的槽位分布、热点和故障恢复,不把“命令不报错”误当成正确性验证。
- 进阶追问与回答:能否在客户端加分布式锁再执行跨槽操作?锁无法把多个主节点和数据库调用变成原子事务,还引入租约、脑裂和死锁风险;应使用状态机与幂等补偿。
- 问题:如何设计 Redis(远程字典服务)高可用监控指标,避免只盯存活探针?
- 口述答案:存活探针只能说明进程能否响应,不能说明复制、路由、容量和业务正确性。我会按五层建指标。节点层看内存水位、碎片、CPU(中央处理器)、事件循环延迟、慢命令、网络、持久化 fork(创建子进程)停顿和输出缓冲。复制层看主从连接状态、复制偏移量差、复制延迟、全量同步次数、积压缓冲命中与
min-replicas-to-write(最少副本写入数)拒写次数。控制面层对 Sentinel(哨兵)看 SDOWN(主观下线)、ODOWN(客观下线)、纪元、领导者选举和切换耗时;对 Cluster(集群)看 PFAIL(疑似故障)、FAIL(确认故障)、槽覆盖、配置纪元、MOVED(永久重定向)与 ASK(临时重定向)比例。客户端层看连接池耗尽、重试、超时、拓扑刷新和每个节点的流量倾斜。业务层最重要,监控缓存版本落后、回源 QPS(每秒查询率)、库存守恒差、支付状态回退、事件积压年龄和重建覆盖率。告警应基于持续时间与组合条件,例如复制延迟上升同时副本数不足,比单点瞬时阈值更可靠。每次切换后自动生成时间线,才能验证恢复目标并指导参数调整。 - 进阶追问与回答:为什么要监控 MOVED(永久重定向)?少量是客户端拓扑自愈的正常现象,持续激增则可能意味着迁槽、客户端版本问题或槽位反复变更,会直接抬高延迟与错误。
- 问题:缓存全失或 Cluster(集群)大面积不可用时,如何保护数据库并恢复服务?
- 口述答案:第一优先级不是立刻把所有请求打到数据库,而是按权威性和用户价值分级。库存扣减、支付状态确认和关键任务领取继续走权威数据库,但按数据库安全容量设置全局回源闸门、业务幂等和优先级;商品列表、轨迹摘要和后台查询可返回带更新时间的旧值、静态降级或明确繁忙。第二步关闭无上限客户端重试,启用请求合并,确保同一键的并发未命中只有一个回源者,其他请求等待短时间或读取有界旧值。第三步确认故障范围:节点、网络、槽位覆盖、客户端连接、迁移或内存淘汰,并保存控制面和业务证据。恢复缓存时不能全量并发预热,应根据历史热集、租户与槽位分批加载,受同一回源令牌限制;缓存写入携带业务版本,防止预热快照覆盖实时更新。实时事务事件持续失效或更新,后台抽样核对权威版本与缓存版本。若是 Sentinel(哨兵)或 Cluster(集群)切换造成回退,用数据库和事件流重建,不从旧缓存盲目复制。演练验收应包括最大回源 QPS(每秒查询率)、数据库连接等待、恢复时间、版本差和核心业务成功率。这样缓存故障成为性能降级,而不成为库存超卖、重复扣款或任务重复执行。
- 进阶追问与回答:为什么不立即全量预热?全量预热本身会成为数据库洪峰,还可能把旧快照写回缓存;应按热度、版本和统一容量预算渐进恢复。
- 问题:如何向面试官说明 Redis(远程字典服务)高可用方案的“一致性边界”,避免过度承诺?
- 口述答案:我会先把“高可用”和“强一致”拆开。主从、Sentinel(哨兵)和 Cluster(集群)通过副本、故障检测和自动切换提高服务连续性;默认复制是异步的,因此客户端写成功不等于副本已持久化,更不等于故障后一定由包含该写的副本接管。WAIT(等待副本确认)和
min-replicas-to-write(最少副本写入数)可以分别缩小单次写和孤立主持续写的风险窗口,但都不是分布式提交协议。读从节点也可能读到旧值,迁槽会有 ASK(临时重定向),脑裂会产生分叉写,Cluster(集群)跨槽操作有明确限制。这些不是“配置不够好”的缺陷,而是系统在低延迟、可用性和简单性上的设计取舍。接着我说明业务分层:会话、排行榜、轨迹摘要等可定义最大陈旧时间并从权威源重建;库存扣减、支付、账务和任务副作用由数据库状态机、唯一约束、流水、fencing token(栅栏令牌)与对账保证。缓存读模型带版本和更新时间,事务后用可靠事件失效或更新,故障后按版本与事件位点恢复。最后我给出可验证指标:复制延迟、切换时间、缓存版本差、回源预算、库存守恒和金额对账。这样不是说“Redis(远程字典服务)不可靠”,而是明确它承担的可靠性层级及其与业务正确性的接口。 - 进阶追问与回答:如果业务要求零丢失怎么办?先定义是零业务副作用丢失还是零缓存状态丢失;前者应采用权威事务与可重放日志,不能仅通过提高 Redis(远程字典服务)副本数承诺。
- 问题:请给出一套主从、Sentinel(哨兵)和 Cluster(集群)的故障演练计划,以及验收标准。
- 口述答案:演练要从单点故障扩展到真实失败组合。主从层先验证主节点重启、从节点断连、复制延迟、全量重同步和最后写窗口:记录主从偏移量、客户端确认语义和恢复后的版本差。Sentinel(哨兵)层模拟主机宕机、单向网络分区、Sentinel(哨兵)自身隔离、旧客户端地址未刷新和旧主恢复,验证 SDOWN(主观下线)、ODOWN(客观下线)、quorum(法定票数)、领导者选举、旧主围栏与客户端重连;同时确认脑裂期间业务唯一键能拒绝重复副作用。Cluster(集群)层演练单主故障、从节点故障、Cluster Bus(集群总线)异常、槽未覆盖、热槽迁移、扩容限速、缩容和客户端处理 MOVED(永久重定向)/ASK(临时重定向)。所有演练都要注入数据库回源压力,验证回源闸门、请求合并、旧值降级和预热不会打穿权威存储。验收分技术与业务两类:技术上看检测、选举、客户端恢复、槽覆盖、复制延迟、错误率和 P99(99 分位响应时间);业务上看库存永不超卖、支付同一请求号只有一次副作用、任务执行状态单调、轨迹不因乱序倒退、缓存可从事实重建。每次保存分钟级时间线、日志、指标、参数和改进项,下一轮复测关闭项,而不是只做一次“拔网线能切换”的演示。
- 进阶追问与回答:能在生产演练吗?可以在严格隔离、灰度、容量预留和明确回滚条件下做受控演练;先在预生产复现完整数据分布和热度,再逐步扩大生产范围,绝不在无保护条件下制造脑裂。
11. 复习清单
- 能按容量、恢复目标和跨槽比例选择单机、主从、Sentinel(哨兵)或 Cluster(集群)。
- 能说清客户端成功、复制确认、持久化确认和新主继承不是同一件事。
- 能区分 SDOWN(主观下线)、ODOWN(客观下线)、quorum(法定票数)与多数领导者投票。
- 能画出 Sentinel(哨兵)脑裂时旧主继续写、新主提升和旧写丢失的时序。
- 能说明 WAIT(等待副本确认)与
min-replicas-to-write(最少副本写入数)的收益和边界。 - 能计算键到 16384 个 slot(槽)的 CRC16(循环冗余校验 16 位)路由,并设计最小 hash tag(哈希标签)。
- 能正确处理 MOVED(永久重定向)、ASK(临时重定向)、ASKING(允许访问导入槽)和 CROSSSLOT(跨槽错误)。
- 能说明 Gossip(流言传播)、PFAIL(疑似故障)、FAIL(确认故障)与配置纪元的职责。
- 能制定按字节、QPS(每秒查询率)和热度加权的迁槽、扩缩容方案。
- 能用权威数据库、幂等、版本、fencing token(栅栏令牌)和对账守住库存、支付与任务正确性。
