老孙在城里经营着「满汉楼」连锁餐厅,总店开在城东,分店开在城西。两家店共用同一套电子菜单,客人在任意一家都能点到一样的招牌菜。老孙最怕的不是客人挑剔,而是城东店某天半夜煤气爆炸——菜单数据烧了不说,明天中午的几百桌宴席怎么出菜?于是他做了三件事:每半小时把城东店的菜单和订单备份到城西店;两家店平时同时营业、同时接客;万一城东店真出了事,城西店能在十分钟内顶上,把客人全部接住。这套「双厨房保险」,就是容灾与多活架构的雏形。数据如同菜谱,机房如同厨房,流量如同客流,而灾难就是那场可能永远不会来、但来了就要命的大火。本文要讲的,就是如何把这两间厨房设计得既能各自营业,又能互相兜底。

岗位关联

容灾与多活是后端架构师的必修课,掌握这套设计方法,你就能在面试中讲清楚「RPO/RTO」「双活/多活」「故障切换」这些高频考点,也能在实际工作中为关键业务设计出扛得住灾难的高可用系统。

目录
  1. 可视化演示
  2. 容灾能力分级
  3. 同城双活架构
  4. 异地多活架构
  5. 数据同步机制
  6. 故障切换策略
  7. 一致性冲突处理
  8. 多活设计原则
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示同城双活同步复制实时双写、异地多活五节点异步复制RTO/RPO指标、故障切换流量路由与恢复时间线三阶段。

本节配有交互动效(进入视口后自动加载,约 3KB)。

同城双活:同步复制实时双写 DC1与DC2同城距离小于100km 同步复制保证数据强一致性 负载均衡双活对外服务 RTO < 30s, RPO = 0 同步双写零数据丢失双活服务

容灾能力分级

满汉楼老板老孙心里有本账:如果明天城东店因煤气爆炸暂停营业,城西店能立刻接住多少订单?账本能找回几成?这本账的答案,就是容灾能力的等级。老孙的评估逻辑其实只有两个数字:一是「最坏情况下能丢几天账本」,二是「要多久才能重新开火炒菜」。把这两句话翻译成技术语言,就是容灾架构里最核心的两个指标——RPO 与 RTO。

RPO(恢复点目标)回答「允许丢多少数据」。如果老孙每半小时备份一次菜单,那么火灾最多烧掉半小时的新增订单,这半小时就是 RPO。RTO(恢复时间目标)回答「需要多久恢复业务」。城西店接到通知后十分钟内开出第一桌菜,这十分钟就是 RTO。这两个指标共同定义了业务的容灾等级:RPO 越小,数据越安全;RTO 越小,恢复越快,但背后付出的成本也越高。

在工程实践中,我们还常用可用性公式来量化系统的可靠程度。一台服务器稳定运行的日子叫「平均故障间隔时间」(MTBF),出故障后修好的耗时叫「平均修复时间」(MTTR)。两者拼在一起,就得到可用性:

对老孙来说,RPO 与 RTO 就是给两家分店立的「保底承诺」:RPO 决定食材数据最多能丢多少,RTO 决定重新点火要多久。等级越高,备用的厨师、食材、水电就越多,成本水涨船高。容灾设计的第一步,从来不是急着买设备,而是先问业务:「这道菜,你能接受晚几分钟上桌?又能接受丢掉几成订单?」

A=MTBFMTBF+MTTR\displaystyle A = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}}
这个公式说的是:可用性 = 稳定工作的日子 ÷(稳定工作的日子 + 修修补补的日子)。修得越快,可用性越高,系统越让人放心。
  1. 分子 MTBF平均故障间隔时间,表示系统两次故障之间能稳定运行的平均时长,是系统可靠性的直接体现。
    MTBF=∑正常运行时间故障次数\text{MTBF} = \frac{\sum \text{正常运行时间}}{\text{故障次数}}
  2. 分母整体MTBF 加 MTTR 构成系统总时间,包含正常阶段和故障修复阶段这两个互斥部分。
    总时间=MTBF+MTTR\text{总时间} = \text{MTBF} + \text{MTTR}
  3. 比值 A正常时间占总时间的比例,数值越接近 1,代表系统全年可用水平越高。
    A=MTBFMTBF+MTTR≈1\displaystyle A = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}} \approx 1

同城双活架构

老孙在城西开店时订了一条规矩:城东和城西,两家店必须同时挂牌营业,谁也不能只当「备胎」。客人去哪家都能点到全套招牌菜,两家店的厨房都开着火。这就叫「双活」——不是「一主一备」,而是「双主共同服务」。同城双活架构,就是在同一座城市部署两个数据中心,两个机房同时对外提供读写能力。

实现双活的关键在于网络质量。城东到城西的光缆延迟通常在 1 到 2 毫秒,这个速度足够两个机房的数据库实时互相拷贝数据。流量进入时,由负载均衡器按权重把请求分摊到两个机房:设定权重后,流量分配比例可以用一个简单的公式表达:

同城双活并非没有代价。两个机房同时接收写请求,数据一致性就成了头等难题——城东改了菜单,城西的顾客必须马上看到。解决方案是让两个机房的数据库通过同步复制保持强一致:一笔写操作只有两边都确认成功,才向用户返回成功。老孙把这套逻辑通俗地总结为「一家改菜单,另一家的点菜平板必须同时亮起新菜名」。同城双活适合对一致性要求高、又希望提升机房利用率的业务,比如支付交易、在线下单。它用「双厨房同时开火」的方式,把灾难切换时间从小时级压缩到了秒级。

ratioA=WAWA+WB\displaystyle \text{ratio}_A = \frac{W_A}{W_A + W_B}
权重就好比两家店的灶台数量:城东有 3 个灶台,城西有 1 个灶台,那么 75% 的客流会被引导到城东。权重越大,接的客越多。
  1. 分子 W_A机房 A 的流量权重,代表该机房能承载的流量配额,权重由机器配置和带宽决定。
    WA=机房A的配额系数W_A = \text{机房A的配额系数}
  2. 分母求和两个机房的权重之和作为归一化基准,保证两边的百分比加起来恰好是 100%。
    WA+WB=总配额W_A + W_B = \text{总配额}
  3. 比值 ratio_A机房 A 分到的流量占比,值越接近 1 表示承担的流量越多,另一个机房相应减少。
    ratioA=WAWA+WB\text{ratio}_A = \frac{W_A}{W_A + W_B}

异地多活架构

同城双活虽然好,但挡不住整座城市停水停电的大灾难。老孙的生意越做越大,他决定把第三家分店开到三百公里外的临江市。三家店分布在两个城市,任何一座城市出了天灾,另外一座城市的厨房照样能出菜。这就是异地多活——多个城市的数据中心同时对外提供服务,用来抵御地域级灾难。

异地多活的难点在于「远」。临江到城东的光缆延迟可能有 30 到 50 毫秒,数据库做不到实时同步。于是架构师引入「单元化」设计:把用户按 ID 划分成多个「单元」,每个单元是一个自包含的完整业务栈,数据按用户维度拆分。请求入口通过路由层决定由哪个单元处理:

单元化让每个单元只管自己的用户,跨城市的数据同步压力大幅降低。老孙的三家店虽然相隔数百公里,但每家店只服务自己管辖的那批老顾客,菜单可以每天夜里批量同步一次。异地多活的典型场景是面向全球或全国用户的大型互联网应用,比如电商大促、社交平台,它们需要就近接入、大规模扩展和地域容灾。付出的是「最终一致」的代价——临江店看到的菜单可能比城东店晚几秒钟,但绝大部分情况下,客人根本察觉不到。

shard=hash(user_id) mod N\displaystyle \text{shard} = \text{hash}(\text{user\_id}) \bmod N
路由层像餐厅门口的取号机:根据客人的会员号尾号,把客人引导到对应的分店。尾号是 0 的去 A 厨房,尾号是 1 的去 B 厨房,每个厨房都自成一体。
  1. 哈希函数 hash把用户 ID 映射为一个无规律的整数,保证用户被均匀分散到各个单元,避免某个单元堆积过多用户。
    hash(user_id)=一个均匀分布的整数\text{hash}(\text{user\_id}) = \text{一个均匀分布的整数}
  2. 取模运算 mod用哈希值除以单元数量 N 取余数,余数范围恰好是 0 到 N-1,对应 N 个单元。
    shard=hash(user_id) mod N\text{shard} = \text{hash}(\text{user\_id}) \bmod N
  3. 单元数 N多活部署的单元总数,N 越大扩展性越强,但跨单元数据交互的复杂度也随之上升。
    N=单元总数N = \text{单元总数}

数据同步机制

双活也好、多活也罢,所有厨房都必须共用同一本菜单。老孙的电子菜单系统每天要处理几万次更新,怎么让两家店的平板电脑始终显示一致?靠的就是数据同步机制。同步方式分两种:同步复制与异步复制,它们的取舍贯穿整个容灾设计。

同步复制像「电话确认订菜」:城东店接到大单,必须打电话到城西店确认对方也记下了,才敢向客人说「收到」。这个模式数据零丢失,但每次写入都要等最慢的那个机房响应。一笔写操作的耗时,取决于两个机房各自确认耗时的最大值:

异步复制则像「拍照片发工作群」:城东店先记下菜单,抽空再把照片发到群里,城西店有空了自行同步。本地写入飞快,但如果城东店在照片发出前着火,这批改动就丢了。工程上常采用「半同步」折中:主库写入成功后,至少一个备库确认收到再返回,兼顾性能与安全。数据同步通道还必须具备断点续传能力——网络抖动导致复制中断时,能从断点继续补偿,而不是从头再来。老孙的厨房虽然有两套点菜系统,菜单同步的「电话线」永远要保持畅通,这就是数据同步机制在容灾架构中的分量。

Tsync=max⁡(TA,TB)\displaystyle T_{\text{sync}} = \max(T_A, T_B)
同步复制的时间等于最慢厨房的反应时间。两个厨房一个 2 秒回话、一个 5 秒回话,整单确认就要等 5 秒。安全性高,但速度被最慢的环节拖住。
  1. 机房 A 耗时 T_A机房 A 完成本地写入并返回确认所花费的时间,包含磁盘落盘和网络回包。
    TA=机房A写入确认耗时T_A = \text{机房A写入确认耗时}
  2. 机房 B 耗时 T_B机房 B 同样完成写入确认的耗时,两地硬件性能差异会导致两边耗时不同。
    TB=机房B写入确认耗时T_B = \text{机房B写入确认耗时}
  3. 取最大值 max同步复制必须等待所有机房都确认,所以总耗时由最慢的机房决定。
    Tsync=max⁡(TA,TB)T_{\text{sync}} = \max(T_A, T_B)

故障切换策略

再周全的预案,也拦不住意外真发生的那一刻。某个周五晚市高峰,城东店厨房的电路突然爆掉,油烟弥漫,灶台熄火。这时候最怕的不是火,而是「城东店的订单系统还在继续接单」——客人下了单却没人做菜。故障切换策略要解决的,正是「如何知道出事了、如何把客流全部导走、如何安全回归」这一整套流程。

首先靠心跳检测发现问题。每个机房的监控代理会周期性向对端发送心跳包,就像店员每 30 秒用对讲机报一声「正常」。如果连续多次没听到对讲机回应,就判定对方故障,而不是立刻切换——因为单次丢包可能只是网络抖动。判定逻辑可以写成:

切换决策确定后,流量调度系统会把原本指向故障机房的请求全部转到健康机房。老孙的应急手册写得明明白白:先停掉城东店的线上接单(摘流量),再由城西店接管全部订单(切流量),最后核对两边订单数据有没有缺口(对账)。灾难恢复后不能急着把流量全切回去——新开火的厨房要先观察一段时间,确认稳定后才逐步回切,这就是「灰度回切」。老孙每季度都会组织一次消防演习,故意让城东店「模拟故障」跑一遍完整切换流程。因为真正的灾难来临时,没有演练过的切换流程,才是最危险的不确定因素。

Nmiss≥K\displaystyle N_{\text{miss}} \ge K
连续 K 次没听到对方报平安,才认定这家厨房真出了事。K 太小容易误判,K 太大反应太慢,一般取 3 到 5 次。
  1. N_miss 丢失计数连续未收到心跳的次数,每次超时未到就加一,收到一次心跳就清零。
    Nmiss=连续丢失的心跳包数量N_{\text{miss}} = \text{连续丢失的心跳包数量}
  2. K 阈值预先设定的判定阈值,工程中通常取 3 到 5,兼顾误判率与切换速度。
    K=故障判定阈值K = \text{故障判定阈值}
  3. 比较判定当丢失次数达到阈值,系统认定机房故障,触发后续的流量切换流程。
    Nmiss≥K⇒触发切换N_{\text{miss}} \ge K \Rightarrow \text{触发切换}

一致性冲突处理

多活架构最让人头疼的问题,是两间厨房同时改了同一道菜谱。城东店把「红烧肉」的糖从 50 克改成 30 克,城西店在同一天把糖改成 60 克,晚上同步菜单时,到底听谁的?这就是数据一致性冲突。它源于分布式系统的一个铁律:只要数据存在多个副本,并发写入就必然产生冲突。

要理解怎么处理冲突,先要认识一致性模型。强一致性要求任何时刻所有副本读到的数据都相同,实现代价极高——每次写都要全局锁,相当于所有厨房改菜都必须等总店长批准。最终一致性则允许副本间短暂不一致,但保证「过一段时间后」所有副本收敛到相同状态。多活架构几乎必然选择最终一致,于是冲突消解就成了必修课。一种通用做法是引入「版本号」机制:每条数据带一个版本号,版本号大的覆盖版本号小的。如果两边同时产生新版本,就交给总店长(仲裁节点)裁决,或者按业务规则合并。冲突概率的估算公式也很直观:

老孙的解决方案很实在:把菜单按菜系分区——川菜归城东店管,粤菜归城西店管,两店各改各的菜,交集极小。对应到架构设计中,就是「数据就近写入、按归属路由」,从源头减少冲突。万一真撞了,版本号加仲裁兜底。一致性冲突处理的核心思想,不是消灭冲突——在分布式世界里冲突消灭不了,而是设计好「擦枪走火之后怎么办」的机制。

Pconflict=PA×PB\displaystyle P_{\text{conflict}} = P_A \times P_B
冲突概率等于两边同时改菜的概率相乘。两个店各自有 1% 的概率在同一天改同一条菜谱,冲突概率就是万分之一。业务越割裂,冲突越罕见。
  1. P_A 单点概率机房 A 对某条数据发起修改操作的概率,由业务在 A 单元的活跃程度决定。
    PA=A机房的修改发生概率P_A = \text{A机房的修改发生概率}
  2. P_B 单点概率机房 B 对同一条数据发起修改的概率,两边用户都可能操作同一份业务数据。
    PB=B机房的修改发生概率P_B = \text{B机房的修改发生概率}
  3. 乘积关系两边同时修改同一条数据是独立事件,同时发生的概率等于各自概率的乘积。
    Pconflict=PA×PBP_{\text{conflict}} = P_A \times P_B

多活设计原则

讲完这么多,你可能会问:是不是所有业务都该上多活?老孙的答案是否定的。隔壁的面馆只有一间铺子,菜单就挂在墙上,搞两间厨房纯属浪费。多活架构有它严格的前置条件,不是什么业务都适合。

第一个条件是业务能否按用户维度拆分。像满汉楼的菜单按菜系分区,用户的订单天然属于某个店,拆分容易;但像银行的账户总账,每一笔交易都要求严格读写一致,拆分的代价极高。第二个条件是网络延迟。同城双活要求机房间延迟在毫秒级,异地多活虽然放宽到几十毫秒,但核心业务链路如果无法容忍这个延迟,多活就无从谈起。第三个条件是自动化能力——流量调度、故障切换、数据校验,都必须由系统自动完成,靠人工打电话确认的时代已经过去了。这些条件综合起来,可以用一个容量公式帮助评估冗余成本:

老孙给满汉楼定的落地路线是「先同城、再异地、后多活」:先在城东城西做同城双活,跑顺切换流程;再在临江开设第三家店,把核心订单单元化;最后才逐步扩大异地多活的范围。每一步都伴随容量、带宽、运维人力的重新评估。多活设计没有银弹,它是在业务连续性、技术复杂度和投入成本之间走钢丝。老孙最后总结的一句话,值得每个架构师记在工位上:「厨房开几间不重要,重要的是无论哪一间突然熄火,客人的菜都有人管。」

Credundant=NN−k\displaystyle C_{\text{redundant}} = \frac{N}{N - k}
这句话的意思是:如果有 N 间厨房,要允许同时坏掉 k 间还能正常营业,那总的接待能力就得放大到 N ÷ (N-k) 倍。
  1. 分子 N多活架构中的单元总数,代表一共有多少个机房参与提供服务。
    N=多活单元总数N = \text{多活单元总数}
  2. 分母 N-k减去允许同时故障的单元数 k 后,仍然存活的单元数量,这些单元要扛下全部流量。
    N−k=存活单元数N - k = \text{存活单元数}
  3. 比值结果所需冗余倍数,例如 3 个单元容忍 1 个故障,冗余倍数是 3/2,即每个单元要预留 50% 的缓冲容量。
    Credundant=32=1.5C_{\text{redundant}} = \frac{3}{2} = 1.5

AI 应用场景

金融支付系统「两地三中心」容灾

阶段:系统架构设计 / 高可用保障

解决的问题:传统银行核心系统采用「单中心+冷备」模式,灾难发生时数据丢失严重、恢复长达数小时,难以满足监管对 RPO 趋近于零、RTO 小于 30 分钟的要求。AI 驱动的智能流量调度与故障预测系统,可以提前识别机房异常,辅助容灾系统自动完成切换决策。

具体案例:某股份制银行按照「两地三中心」布局,生产中心设在同城 A、B 两个机房,异地再设一个灾备中心。日常 A、B 双活分摊交易流量,AI 运维平台实时监控全链路时延与硬件健康度;当 A 机房出现存储亚健康状态时,平台提前预警并将流量平滑切至 B 机房,整个过程柜面业务无感知。

电商大促「异地多活」扩展

阶段:容量规划 / 流量调度

解决的问题:双十一大促期间,单机房容量存在上限,且一旦机房故障将导致全国用户无法下单。AI 在其中的价值体现为:根据历史流量预测各区域请求量,动态调整单元化路由权重,使各单元负载均衡,同时支持故障单元的秒级摘流。

具体案例:某头部电商平台在全国 3 个城市部署单元化集群,按用户 ID 哈希划分单元。大促前 AI 预测华东地区流量激增,自动把华东用户的单元权重上调;大促中出现华南机房光缆抖动,路由层自动将华南流量切换到邻近单元,用户仅感受到一次轻微的页面刷新。

在线教育「同城双活」保障

阶段:实时音视频链路高可用

解决的问题:在线课堂对实时性极其敏感,单机房故障会导致上万名学生集体掉线。同城双活让两个机房同时承载上课流量,AI 质量监测模型实时评估各链路丢包率与延迟,自动将学生调度到质量更优的机房接入点。

具体案例:某在线教育公司在北京两个机房部署双活集群,直播推流和互动消息均双写。某一机房因运营商线路故障导致丢包率飙升,AI 模块在 3 秒内完成判定,将 2 万名学生的互动消息链路切换到另一机房,课堂无感继续。

Python 演示

python64 行
"""双活容灾切换模拟器: 演示流量路由、故障检测、切换与恢复"""import time  class DataCenter:    """数据中心(机房)"""     def __init__(self, name, capacity):        self.name = name        self.capacity = capacity        self.healthy = True        self.requests = 0     def serve(self, count):        self.requests += count        return self.requests  class FailoverSimulator:    """双活容灾切换模拟器"""     def __init__(self):        self.dc_a = DataCenter('A机房', 1000)        self.dc_b = DataCenter('B机房', 1000)        self.heartbeat_interval = 1        self.last_heartbeat_a = time.time()        self.last_heartbeat_b = time.time()     def route_requests(self, count):        healthy = [dc for dc in (self.dc_a, self.dc_b) if dc.healthy]        if not healthy:            return {}        share = count // len(healthy)        for dc in healthy:            dc.serve(share)        return {dc.name: share for dc in healthy}     def check_heartbeat(self):        now = time.time()        if now - self.last_heartbeat_a > self.heartbeat_interval:            self.dc_a.healthy = False        if now - self.last_heartbeat_b > self.heartbeat_interval:            self.dc_b.healthy = False     def simulate(self):        print('=== 双活容灾演练开始 ===')        for i in range(3):            self.check_heartbeat()            result = self.route_requests(1000)            print('第%d轮流量分配: %s' % (i + 1, result))         self.dc_a.healthy = False        print('>>> A机房发生故障, 触发切换')        result = self.route_requests(1000)        print('切换后流量分配: %s' % result)         self.dc_a.healthy = True        print('>>> A机房恢复, 开始回切')        result = self.route_requests(1000)        print('回切后流量分配: %s' % result)  if __name__ == '__main__':    FailoverSimulator().simulate()
目的:用两个机房对象模拟双活架构的流量路由与故障切换,直观演示「健康机房均分流量、故障机房摘流、恢复后重新参与分配」的完整闭环。
  1. healthy = [dc for dc in (self.dc_a, self.dc_b) if dc.healthy]过滤出当前健康的机房列表,只有健康机房才能接收流量。
  2. share = count // len(healthy)把请求总量整除均分给健康机房,保证每个存活节点压力均等。
  3. dc.serve(share)每个机房对象记录自己承接的请求量,模拟真实场景下的负载计数。
  4. if now - self.last_heartbeat_a > self.heartbeat_interval模拟心跳超时检测:超过阈值未收到心跳即判定机房失联。
  5. self.dc_a.healthy = False手动将机房 A 置为故障,触发切换逻辑,对比切换前后的流量分配结果。
  6. FailoverSimulator().simulate()程序入口,按「双活 → 故障 → 切换 → 恢复 → 回切」的顺序完整执行演练。

常见误区

误区很多初学者以为,只要把数据库从库同步到另一个机房,就算完成容灾了。「主库在 A 机房,备库在 B 机房,A 挂了 B 能顶上,这不就是容灾吗?」

正解主从复制只是数据层面的备份,容灾还要求「业务系统能够切换」。满汉楼光在城西存了一份菜单没用,城西店还得有厨师、有灶台、有接客流程,才能在城东失火时真正出菜。容灾 = 数据备份 + 业务就绪 + 切换能力,三者缺一不可。很多系统备库数据是齐的,但应用层没有部署、负载均衡没有配置,真正灾难发生时照样恢复不了。

为何常见:因为数据是最直观的资产,教材和文档最爱讲复制技术,却很少强调「切换流程」同样是容灾的组成部分,导致认知停留在「数据在就等于业务在」。

误区「我们用了同步复制,RPO 是 0,所以任何情况下都不会丢数据!」这是面试和工作中高频出现的错误认知。

正解RPO = 0 代表主备数据在正常链路下完全同步,一旦主库故障,已确认的写入数据全部在备库存在。但它抵挡不住「主库瞬间被物理摧毁」这类极端场景——比如整个机房被洪水淹掉,而最后一笔同步还没完成,那几毫秒的数据窗口就丢了。RPO = 0 是在「链路健康」的前提下的承诺,不是物理学意义上的绝对不丢。老孙的厨房可以做到「菜单实时同步到城西」,但也挡不住城西店同时被雷劈。

为何常见:「等于 0」听起来像绝对保证,人们天然喜欢用极端词汇给自己安全感,忽略了分布式系统中不存在零成本的绝对承诺。

误区「既然是双活/多活,两个机房都在服务,那一个挂了另一个自然就接管了,不用切换了吧?」许多技术方案汇报中常听到类似说法。

正解多活只是常态运行模式,故障时依然需要「切换」动作——虽然不需要把全部流量从零切过去,但需要执行「摘除故障单元」「重新分配权重」「对账补偿」等步骤。双活机房 A 故障后,流量路由系统必须主动把 A 的权重降为零,否则请求还会持续打到故障机房导致超时。多活降低了切换的代价,但并没有消除切换的必要性。就像两家店同时营业,一家着火后,门口仍然需要有人举着牌子引导客人去另一家。

为何常见:「活」字容易让人产生「自动全搞定」的错觉,忽略了流量调度、健康检查这些支撑组件本身也是需要配置和运维的。

误区「切换流程在文档里写清楚就行了,没必要真演练,万一演练把系统搞挂了怎么办?」很多团队因此把容灾预案停留在 PPT 层面。

正解没有实际演练过的切换流程,在真实灾难发生时几乎必然出问题。首次切换的平均耗时和故障率都远高于演练过的流程,因为真实环境里存在 IP 配置、依赖启动顺序、数据校验脚本等各种文档里没写清的细节。老孙的厨房每季度都要搞一次「模拟火灾疏散」,让新来的厨师也熟悉备用路线。容灾演练应该选择业务低峰期,从「半切换」开始逐步加大范围,把演练本身当作一次发布来对待。

为何常见:演练确实有风险、要投入时间,短期看不到收益,而灾难是低概率事件,人的大脑天然会低估小概率事件的风险。

面试题

Q1. 在容灾架构中,RPO 指的是什么?

单选题 · easy

  • A.恢复时间目标,故障后恢复业务所需时长
  • B.恢复点目标,可容忍的最大数据丢失量← 正确答案
  • C.故障切换流程中流量迁移的最大耗时
  • D.系统从故障发生到告警通知的时间

答案:B。RPO 关注「数据丢了多少钱」,答案 B 正确。A 描述的是 RTO;C 和 D 分别混淆了切换耗时与告警耗时,它们属于恢复流程的时间指标,而非数据丢失量。用满汉楼的比喻理解:RPO 是火灾烧掉了几小时的账本,RTO 是重新开业花了多久。

Q2. RTO 在容灾中表示什么?

单选题 · easy

  • A.数据从生产中心复制到灾备中心的耗时
  • B.系统故障后恢复到可对外服务的最长时限← 正确答案
  • C.一次完整的容灾演练所需的总时间周期
  • D.故障检测到触发告警通知所经历的时间

答案:B。RTO 回答「多久恢复营业」,B 正确。A 是复制时延,C 是演练周期,D 是检测时延,三者都与「业务恢复」无直接关系。老孙的城西店从接到城东失火电话到开出第一桌菜,这个过程就是 RTO。

Q3. 同城双活架构中「双活」的核心含义是什么?

单选题 · easy

  • A.一个机房运行另一个处于冷备待命状态
  • B.两个机房同时对外提供业务读写服务← 正确答案
  • C.两个机房各自只服务本地地域的用户
  • D.生产机房故障后灾备机房才启动服务

答案:B。「双活」的关键是两边都活着、都在接客,B 正确。A 和 D 描述的是主备模式,C 虽然两个机房都在服务但按地域硬切分,缺少互相接管能力。满汉楼两家店同时营业、都做全套菜,才是真正的双活。

Q4. 异地多活中单元化路由通常以什么作为分片依据?

单选题 · mid

  • A.服务器 CPU 的实时使用率高低排序
  • B.用户 ID 的哈希值对单元数量取模← 正确答案
  • C.数据表主键自增序号的全局最大值
  • D.请求到达机房网关所经由的网络路径

答案:B。单元化架构普遍采用「对用户 ID 哈希后对 N 取模」确定归属单元,B 正确。A 是负载均衡的指标而非分片依据;C 的主键自增值无法稳定映射用户;D 的网络路径是物理路由,与业务分片无关。这就像取号机按会员号尾号分配厨房。

Q5. 心跳检测机制的主要目的是什么?

单选题 · mid

  • A.在机房故障前提前备份全部业务数据
  • B.周期性探测节点状态以便及时发现问题← 正确答案
  • C.自动修复宕机节点上的应用进程状态
  • D.降低正常运行时数据中心间的网络流量

答案:B。心跳就是定时「报平安」,让监控系统及时发现失联节点,B 正确。A 是备份系统的职责,C 是故障自愈的职责,D 与心跳目的无关。老孙让两家店每 30 秒用对讲机互报一声,正是为了让火灾发生时能第一时间知道。

Q6. 一次完整的故障切换流程通常包含哪些环节?

多选题 · mid

  • A.故障检测与告警确认← 正确答案
  • B.流量切换与数据校验← 正确答案
  • C.故障恢复与流量回切← 正确答案
  • D.删除业务全部历史日志

答案:ABC。故障切换闭环包含「检测确认 → 切换流量 → 数据对账 → 恢复回切」,A、B、C 正确。D 属于破坏性操作,容灾切换绝不允许清理历史日志,反而要尽可能保留数据用于审计和对账。

Q7. 异地多活相比同城双活最大的技术挑战是什么?

单选题 · mid

  • A.机房之间网络延迟升高导致数据同步变难← 正确答案
  • B.需要购买更多服务器承载同等规模业务
  • C.无法使用负载均衡器分发进入机房的流量
  • D.异地机房无法部署数据库因此只能做静态页

答案:A。异地之间光缆延迟从毫秒级升到几十毫秒,数据同步模式和一致性模型必须改变,A 正确。B 是多活的固有成本而非「最大」挑战;C 和 D 是对异地多活的误解,负载均衡和数据库都能跨地域部署,只是需要更复杂的设计。

Q8. 下列关于同步复制与异步复制的说法正确的有哪些?

多选题 · hard

  • A.同步复制牺牲部分写入性能换取更低丢失风险← 正确答案
  • B.异步复制本地写入快但故障时可能丢数据← 正确答案
  • C.同步复制主库写成功时备库已确认收到数据← 正确答案
  • D.异步复制不可能出现数据不一致的情况

答案:ABC。同步复制要等备库确认才返回,变慢了但数据更安全,A、C 正确;异步复制先写本地再后台同步,速度快但故障窗口内会丢数据,B 正确。D 错误——异步复制天然存在短暂的不一致窗口。满汉楼同步复制像「电话确认订菜」,异步复制像「拍照发群晚点再看」。

Q9. 双活架构中出现「脑裂」时,通常采用的仲裁手段是?

单选题 · hard

  • A.由两个机房各自统计流量并协商最终结果
  • B.引入独立仲裁节点投票决定唯一主节点归属← 正确答案
  • C.停止所有写请求等待人工手动强制恢复数据
  • D.让数据同步通道自动加倍重传以解决冲突

答案:B。脑裂是两机房之间通信中断,但两边都认为对方已死、各自争当主节点。解决标准方案是引入独立的仲裁节点(如 ZooKeeper/etcd)投票选出唯一主节点,B 正确。A 中两个机房已经失联无法协商;C 完全人工干预太慢;D 加倍重传无法解决「谁当主」的归属问题。

Q10. 评估业务是否适合异地多活架构时需要考虑哪些条件?

多选题 · extreme

  • A.业务是否可以按用户维度进行数据拆分← 正确答案
  • B.核心链路能否容忍跨城网络延迟影响← 正确答案
  • C.是否具备自动化流量调度与灰度能力← 正确答案
  • D.是否已经全部迁移到单体巨型应用架构

答案:ABC。异地多活的前提是「业务可分片」「链路可延迟」「调度可自动」,A、B、C 正确。D 恰恰相反——单体巨型应用难以单元化拆分,是落地多活的阻碍而非条件。老孙能开三家店,是因为菜单能按菜系分区、客人能就近分流;若是只有一个大厨房,再开分店也只是个摆设。