分布式理论面试题集:从十个问题收口
属于 S8 分布式理论 · 收口篇 上一篇:容灾与备份恢复
分布式高频问题,每题练到连续追问三层。
Q1:CAP 理论是什么?为什么只能三选二?
分布式系统在一致性(线性一致)、可用性(有限时间内返回非错误响应)、分区容错性中最多同时满足两个。网络分区不可避免,P 必选,实际是 CP 与 AP 二选一。证明:分区后向一侧写、向另一侧读——保 C 另一侧必须拒绝(牺牲 A),保 A 另一侧返回旧值(牺牲 C)。
追问:你的系统是 CP 还是 AP? 按数据粒度分:分布式锁/配置走 CP(etcd),服务发现/缓存走 AP(Eureka/Redis);说明每个选择的一致性要求和降级路径。
Q2:etcd 和 ZooKeeper、Eureka 的区别?
etcd/ZooKeeper 是 CP:多数派确认、分区时少数派拒绝服务,用于锁、选主、配置。Eureka 是 AP:注册中心读旧列表可容忍,整体不可用不可容忍。
追问:注册中心为什么选 AP? 读到已下线实例最多一次请求失败重试;注册中心全挂 = 所有服务找不到彼此 = 全网瘫痪。所以保可用牺牲"一定最新"。
Q3:BASE 理论和 ACID 什么关系?
BASE(基本可用、软状态、最终一致)是 AP 路线的工程化总结;ACID 面向单机强一致事务。BASE 牺牲强一致换可用性,靠补偿收敛。
追问:最终一致怎么落地? 异步复制 + MQ 重试(死信队列)、本地消息表 + 事务消息、对账补偿、重试配幂等、Saga/TCC。任何异步链路必须配对账兜底。
Q4:Raft 怎么选出 Leader?为什么不会脑裂?
Follower 随机选举超时未收到心跳 → term+1 转 Candidate 广播 RequestVote;拿到多数票成 Leader 立即发心跳。投票规则:任期不落后、每任期一票、候选人日志足够新。脑裂防护:每任期一票 + 多数派互斥 + 旧 Leader 分区后无法获得多数确认无法提交。
追问:随机超时解决什么? 选票分裂——多个 Candidate 同时发起谁也拿不到多数,随机化让超时错开快速收敛。
追问:为什么投票要比较日志新旧? 选举限制:保证当选 Leader 包含所有已提交日志(Leader Completeness),否则已提交日志可能在新 Leader 中丢失。
Q5:Raft 的日志什么时候算提交?旧任期日志为什么不能直接提交?
复制到多数派且属于 Leader 当前任期。旧任期条目即使复制到多数派也不能直接提交——新 Leader 可能由不含该条目的节点选出,直接提交会导致状态机分叉;新 Leader 先提交自己任期的一条日志,之后旧条目连带提交。
追问:commitIndex 怎么同步给 Follower? Leader 在后续 AppendEntries/心跳中携带 leaderCommit,Follower 取 min(leaderCommit, 最后匹配日志 index)。
Q6:Raft 集群加一个节点有什么坑?成员变更怎么做?
新节点日志为空,直接参与投票会反复拒绝 AppendEntries 拖慢集群;先以无投票权身份追赶日志再入配置。变更用单节点变更(一次一个,新旧多数派必有交集)或联合共识(旧多数+新多数)。
追问:为什么不能直接切换配置? 旧配置多数派与新配置多数派可能不相交,出现两个 Leader 同时提交不同日志。
Q7:Raft 怎么保证线性一致读?
三种:读走日志(最慢)、ReadIndex(Leader 与多数派确认身份后本地读,etcd 默认)、Lease 读(选举超时内免确认,依赖时钟同步)。
追问:Lease 读有什么风险? 依赖时钟同步假设,极端时钟漂移下可能读到过期值;生产更稳的是 ReadIndex。
Q8:Paxos 和 Raft 你选哪个?为什么?
Raft。可理解可实现:强领导者简化并发控制、成员变更明确、安全性可证明;Paxos 理论正确但 multi-paxos 无统一规范实现易错。事实标准:etcd、Consul、TiKV、MongoDB 副本集都用 Raft。
追问:Raft 的强领导者模型有什么缺点? Leader 是单点(写路径依赖它);Leader 需处理全部写流量;选举期间集群短暂不可写;读走 Leader 时吞吐受单节点限制(可配合 ReadIndex/Lease 缓解)。
Q9:etcd 的 MVCC / Watch / Lease 各解决什么问题?(详解见etcd 详解与工程实践)
MVCC 保留 key 历史版本(revision),支撑 Watch 从指定版本续推 + 事务 CAS 按版本比较;Watch 是长连接流式推增量事件,客户端用"全量拉取 + 增量订阅 + revision 补偿重连"维护本地缓存;Lease 是 key 自动过期机制,绑定租约的 key 在续租停止后到期自动删除——分布式锁/服务注册"持有者崩溃自动释放"全靠它。
追问:watch 风暴怎么防? 只 watch 自己依赖的小前缀(分级注册表)、本地缓存 + revision 补偿、按需订阅;实例量大时提高 TTL、降低 keepalive 频率。
Q10:基于 etcd 的分布式锁怎么实现?Redis 锁和它怎么选?
租约 + Txn(CreateRevision==0) 原子占 key + 后台续租 + 释放时 Txn(Value==token) CAS 删除防误删;锁 TTL 要大于业务最慢耗时(或看门狗续租)。选型:性能优先、可容忍极小概率并发(秒杀缓存类)用 Redis SETNX;必须精确(任务调度去重、选主、配置)用 etcd 强一致。
追问:为什么释放锁要 CAS? 防止 A 的租约过期后 B 拿到锁,A 迟到的 Unlock 把 B 的锁删掉,导致 C 也拿到锁 → 互斥被破坏。
自测清单
十题 + 追问全答上,S8 过关。Raft 高频追问链:选举规则 → 提交规则 → 脑裂 → 成员变更 → 线性一致读;etcd 高频追问链:MVCC → Watch → Lease → 分布式锁 → 注册中心,务必能连续答三层。
强化补充题(能力强化篇:底层存储 / 选主 / 负载均衡 / 降级 / 限流熔断 / 高可用 / 容灾)
对应 能力强化系列 七篇正文,每题练到连续追问两层。
Q11:数据同步为什么不直接复制数据,而要复制日志?
日志是追加写(顺序 IO 快)、带顺序号(可断点续传/对账)、重放即收敛(复制状态机)。binlog(MySQL)、AOF(Redis)、Raft log(etcd)、分区日志(Kafka)都是"复制日志 → 按序重放"的同一思想,差别只在同步严格度:异步丢窗口、半同步近不丢、多数派(Raft)确认不丢已提交。
追问:WAL 为什么能防崩溃丢数据? 提交前先 fsync 日志;崩溃后重放恢复。用"顺序写日志"换"随机写数据页",组提交摊薄 fsync 成本。
追问:B+ 树和 LSM-Tree 怎么选? 读多写少强事务 → B+ 树(InnoDB);写多读少 KV → LSM(RocksDB/TiKV);权衡写放大/读放大/空间放大,LSM 读路径靠 Bloom filter 裁剪。(详解见底层存储与数据同步)
Q12:基于 etcd 的选主怎么实现?和 Redis 锁选主怎么选?
抢同一把带租约的锁(Txn(CreateRevision==0)→Put + 后台续租),备节点 Watch 该 key,leader 崩溃 → 租约过期 → delete 事件 → 重新抢锁。TTL = 故障感知延迟(5~10s 平衡误切换)。Redis 锁是 AP(主从切换丢锁 → 双主),etcd 是 CP(多数派确认不双主)——调度去重可容忍双主用 Redis,元数据/写入口双主=灾难用 etcd。
追问:怎么防"旧主继续写"? 租约只解决"资格",Fencing Token 让存储端拒绝旧 token 的写才是最后防线;新主上任的接管动作必须幂等(CAS + 检查在途资源)。(详解见选主机制详解)
Q13:为什么 gRPC 要客户端负载均衡?策略怎么选?
HTTP/2 长连接多路复用 → 连接数≠流量 → 代理 LB 看不见连接内的 stream,且 gRPC 请求在客户端本地直接发出。客户端 LB:resolver(注册中心→实例列表)+ balancer(选实例)+ subchannel(每实例一条 HTTP/2 连接)。策略:round_robin 简单不自适应;P2C/least_request 按在途请求选,长任务场景(AI 推理)生产主流;一致性哈希用于有状态粘性。
追问:长连接下怎么防失衡? 按在途请求数选(非连接数)+ 慢启动(新实例权重渐增)+ 健康检查(摘除不健康实例)+ max connection age 定期换连。(详解见gRPC 负载均衡详解)
Q14:网络断了怎么降级?超时重试降级隔离怎么配合?
超时定上限(全链路单一预算逐层递减,按下游 P99×系数)→ 重试救瞬时(只重试幂等 + 指数退避 + jitter + 限次数 + 熔断优先,防重试风暴)→ 熔断止放大(Open 快速失败)→ 降级给兜底(返回缓存/默认值/关非核心,开关配置中心下发 + 本地缓存)→ 隔离防扩散(舱壁:线程池/信号量隔离)。
追问:重试风暴怎么发生、怎么防? 下游 1% 超时 × 全员重试 3 次 → 放大数倍流量打挂下游 → 更多人重试。防:只重试幂等、退避+jitter 打散、限次数、熔断优先、全链路预算。(详解见网络通信与降级方案)
Q15:限流算法怎么选?熔断器状态机讲讲?
固定窗口(边界突刺)→ 滑动窗口(按格子滑,消除突刺)→ 令牌桶(允许突发,后端默认,Go rate 包)→ 漏桶(严格匀速,保护下游吞吐)。分布式限流 = Redis + Lua 原子计数,Redis 挂降级为本地限流(宁可多拦不可漏拦)。熔断:Closed 统计失败率(配最小请求数防误判)→ 超阈值 Open 快速失败 → 冷却后 Half-Open 放探测 → 成功回 Closed / 失败回 Open。
追问:AI 剪辑这种长任务服务限流要注意什么? 限并发数(在途任务上限)比限 QPS 更本质——一个视频生成任务占资源几十秒。(详解见限流与熔断)
Q16:高可用和容灾什么区别?RPO/RTO 是什么?
高可用防节点级故障(冗余 + 故障检测 + 故障转移,同机房);容灾防地域级灾难(备份 + 异地副本 + 恢复演练,跨机房/城市)。RPO = 允许丢多少数据(同步级别决定:同步≈0 / 半同步极小 / 异步秒~分钟);RTO = 多久恢复(冷备小时级 → 热备秒级)。两地三中心 = 主城双活(同步复制防机房级)+ 异地灾备(异步复制防城市级)。
追问:副本能替代备份吗? 不能——副本防机器故障且会把"错误"也复制走;备份防逻辑灾难(误删/误改/bug)且能回到错误前,要异地存放 + 定期恢复演练。(详解见高可用机制全景 与容灾与备份恢复)