Skip to content

多机房与异地多活:单元化、RPO/RTO、故障切换、脑裂与回切

“两个机房都部署了应用”不等于双活,“数据库有从库”不等于灾备,“DNS切过去”也不等于业务已经恢复。多机房系统必须同时解决流量入口、服务发现、数据写入归属、复制延迟、消息路由、缓存、任务调度、身份与密钥、故障检测、旧主隔离、切换和回切。

本页以订单、支付和医疗采集平台为背景,解释单地域多可用区、主备、双活、多活和单元化架构的原理与失败窗口。数据库具体复制机制见各数据库专栏,本页关注跨组件的端到端业务正确性。

一、学习目标

  1. 区分高可用、灾备、主备、双活、多活和单元化。
  2. 从业务影响倒推RPO、RTO和依赖恢复顺序。
  3. 解释GSLB/DNS、全局入口、地域网关和单元路由。
  4. 区分全局数据、单元数据和派生数据。
  5. 解释同步、半同步、异步复制的延迟与丢失窗口。
  6. 说明网络分区时为什么必须Fencing旧写主。
  7. 设计跨机房MQ、缓存、定时任务和分布式锁。
  8. 完成故障宣告、切流、数据核对和回切。
  9. 处理读己之写、单调读、全局唯一ID和时钟边界。
  10. 用演练证明RPO/RTO,而不是只写架构图。

二、先把几个概念分清

概念核心目标典型形态常见误解
高可用单组件故障快速恢复同城多AZ、副本、自动选主等于异地灾备
灾备大故障后恢复业务和数据异地备份、备用集群、Runbook备机存在就算可恢复
Active-Passive主站服务,备站待命数据持续复制,故障切换备站从未演练也可用
Active-Active两站同时承接业务分区写入或冲突处理两边可任意写同一数据
Multi-Active多地域同时提供服务单元化、全球路由所有数据全球强同步
单元化用户或业务键固定归属单元路由码、独立应用和数据闭环只是按地域部署Pod

多活的难点不是应用无状态副本,而是“同一业务事实由谁写、两个分区冲突时谁有效、恢复后怎样合并”。

三、RPO与RTO必须由业务倒推

  • RPO:恢复后最多允许丢失多长时间或多少业务数据。
  • RTO:故障发生后,业务最多允许多长时间恢复到约定能力。
text
故障发生时间 = 10:00
最后可恢复数据 = 09:58
实际数据损失窗口约2分钟,RPO约2分钟

业务在10:12恢复
实际恢复时间约12分钟,RTO约12分钟
业务RPO示例RTO示例设计方向
支付账务接近0分钟级强约束写入、同步确认、对账
订单状态秒到分钟分钟级复制、幂等、状态机、补偿
医疗原始采集数分钟或按批次30分钟本地落盘、断点续传、重放
ES搜索视图可重建小时级MySQL事实源、CDC重建
报表小时级小时到天离线重算、备份恢复

RPO=0并不是一句配置。跨地域同步提交会增加写延迟,并在远端不可达时面临“停止写入还是降低保证”的业务选择。要求越强,成本和可用性代价越高。

四、架构演进层级

4.1 单地域多可用区

应用和副本跨AZ部署,数据库主从或共识成员分散故障域。它能抵御单Node、机架或AZ故障,但无法覆盖整个地域控制面、网络或账号级事故。

4.2 异地主备

主地域承接流量,备地域持续接收数据、配置、镜像和密钥。故障时将备站提升。结构简单,但备站长期无真实流量容易配置漂移、容量不足和证书过期。

4.3 双活/多活

多个地域同时服务。若两边任意写同一订单,需要全局同步一致、冲突合并或单写所有权;多数商业系统更常按用户、租户或业务键分区写入,而不是所有记录全局多主。

五、单元化架构

一个单元尽量包含完成业务闭环所需的Gateway、服务、缓存、数据库分片和MQ资源。用户或业务键通过稳定路由码归属某个单元。

mermaid
flowchart TD
    A["用户请求进入全局流量入口"] --> B["认证后得到可信tenantId或userId"]
    B --> C["路由服务计算unitCode"]
    C --> D{"目标单元"}
    D -- "华东单元" --> E["华东Gateway、服务、DB分片、MQ"]
    D -- "华南单元" --> F["华南Gateway、服务、DB分片、MQ"]
    E --> G["本单元完成大部分业务闭环"]
    F --> G

5.1 路由键

路由键应稳定、可验证且覆盖主要数据访问,例如tenantId、userId或accountId。订单号可编码或查询其归属单元。不能完全信任客户端传入X-Unit,应从认证身份或服务端映射得到。

5.2 为什么要让数据闭环

若用户在华东单元,但每个请求仍跨地域访问华南主库,网络分区时单元无法自治,延迟也受跨地域RTT影响。单元化应让主要读写、消息和缓存留在归属单元,只把必要全局信息复制或聚合。

5.3 跨单元业务

转账、跨租户协作等会跨单元。不能直接假装成一个本地事务,应使用全局业务流水、幂等、状态机、可靠消息、补偿和对账;必要时由专门全局服务协调。

六、数据先分类再谈复制

数据类别示例典型策略
单元事实数据用户订单、租户资产归属单元单写,异地复制
全局强约束数据全局账户、唯一配额共识/单主/专门全局服务
全局配置路由表、产品配置有版本发布、签名、缓存
派生数据ES、报表、推荐事件同步,可重建
临时数据Session、缓存可失效重建,事实源兜底

把所有数据都做跨地域强一致,会让每次提交承担WAN延迟和分区可用性代价;把所有数据都异步复制,又无法满足余额、唯一配额等约束。分类决定方案。

七、复制确认点决定丢失窗口

mermaid
flowchart TD
    A["应用向主地域提交事务"] --> B["主库本地日志持久化"]
    B --> C["复制日志发送到远端"]
    C --> D["远端接收并持久化"]
    D --> E["远端回放并对外可读"]

客户端在哪一步收到成功,决定故障窗口:

  • 本地持久化即成功:延迟低,主站永久故障时未复制部分可能丢失。
  • 等远端收到/持久化:RPO更小,但WAN抖动影响提交。
  • 等远端Apply:可见性更强,延迟和可用性成本更大。

“同步复制”也必须问清同步到哪里、需要几个副本ACK、超时后怎样降级。复制延迟指标不能只看秒数,还要看日志位点、字节差和最老未复制事务。

八、多地域读取的一致性问题

8.1 读己之写

用户在主地域提交订单后被GSLB路由到远端只读站,复制尚未到达,会看到订单不存在。可选方案:

  • 会话在短窗口内粘到写入单元。
  • 响应返回写位点,读取等待副本追到该位点。
  • 核心状态读主/归属单元。
  • UI明确显示“处理中”并按业务流水查询。

8.2 单调读

用户已经看到PAID,随后读到延迟副本的PENDING,会发生状态倒退。可用版本号、状态机序号、会话位点或路由粘性防止返回更旧版本。

8.3 时钟不能决定业务真相

跨机房时钟会漂移,不能只用update_time较大者覆盖另一边。应使用数据库提交序、业务版本、全局流水、向量/混合逻辑时钟等与场景匹配的顺序依据。

九、脑裂与Fencing

网络分区时,旧主可能仍能服务本地客户端,新站又被提升为主。如果两边同时写相同数据,就会形成冲突事实。

mermaid
flowchart TD
    A["主备之间网络中断"] --> B["控制面决定提升备站"]
    B --> C["先隔离旧主网络、存储或写权限"]
    C --> D["颁发更高Epoch或Fencing Token"]
    D --> E["新主写入必须携带新Epoch"]
    E --> F["数据库或外部资源拒绝旧Epoch写入"]
    F --> G["确认安全后切换业务流量"]

仅在新站执行“promote”不够。必须证明旧主不能再写:STONITH、撤销租约、网络隔离、存储Fencing、数据库只读和Epoch校验都可能是手段。Fencing检查必须落在真正副作用资源上,不能只在客户端内存判断。

十、GSLB、DNS和全局入口

全局入口可按地域、健康、容量和策略把用户引向站点。DNS切换有缓存链:权威DNS、递归DNS、OS/JVM、客户端和已有连接。TTL降低只能减少部分缓存时间,不能强制所有客户端立即刷新。

已有HTTP/2、gRPC、WebSocket连接不会因DNS记录改变自动迁移。还要处理:

  • 全局LB健康检查是否覆盖真实业务。
  • 站点入口和内部依赖是否同时健康。
  • Session和Token能否跨站验证。
  • 证书、WAF规则和密钥是否同步。
  • 流量切入速度是否超过备站预热容量。

十一、跨机房MQ

常见方案:

  • 每单元独立Topic/集群,本单元生产消费,异步复制必要事件。
  • 全局MQ跨地域复制,但要明确Leader位置和WAN故障行为。
  • Outbox/CDC把事实事件从归属数据库同步到其他单元。

跨站复制可能重复、延迟和乱序。消费者必须使用稳定eventId、业务版本和幂等状态机。故障切换后不能从旧、新两个集群同时消费却共用不安全副作用。

消息Lag应按单元、Topic、Partition和复制链路观察。切换前记录生产位点,切换后核对缺口、重复范围和最老事件。

十二、缓存、任务与锁的多活边界

12.1 Redis缓存

缓存可以每单元独立,数据库事实变化通过事件失效。不要把跨地域Redis复制当强一致账本;切换后允许缓存丢失并有界重建,防止全量缓存雪崩回源。

12.2 定时任务

两个集群中的CronJob、XXL-JOB执行器可能同时运行。任务本身要有业务幂等、分片归属和Epoch/Fencing;“备站平时关闭Scheduler”也要确保切换时配置状态可靠。

12.3 分布式锁

两个地域各自Redis锁无法提供全球互斥。核心正确性应由归属单元、数据库唯一约束、状态机和Fencing保证。跨WAN强一致锁会增加延迟和分区不可用性,应先重新设计数据所有权。

十三、全局ID与路由信息

Snowflake类ID需要唯一workerId和时钟回拨保护;数据库号段要按单元分配;UUID全局碰撞概率低但不携带路由信息。

商业系统可把unitCode编码在独立字段或受版本管理的ID结构中。不能永远依赖位段不变,解析器应识别版本;敏感地域和租户信息也不应直接暴露在公开ID中。

十四、故障切换不是一个按钮

mermaid
flowchart TD
    A["监控发现主站异常"] --> B["多证据确认影响与故障范围"]
    B --> C["冻结发布、任务和高风险写入"]
    C --> D["Fencing旧主并记录复制位点"]
    D --> E["评估数据缺口和备站依赖健康"]
    E --> F["提升数据库、MQ与必要控制面"]
    F --> G["小流量切入并验证核心业务"]
    G --> H["分批放量并持续核对业务事实"]

切换顺序应由依赖图决定,通常先恢复身份、密钥、配置、数据库、MQ、缓存基础能力,再开放应用流量。应用先切过去但数据库仍只读或证书过期,会把基础设施故障变成大量业务错误。

故障宣告需要明确权限和证据。自动切换速度快,但误判会造成脑裂;人工切换稳妥但RTO更长。可采用自动检测、自动准备、关键提升动作双人确认的分级策略。

十五、回切比切换更容易被低估

旧主恢复后不能直接把流量切回:

  1. 保持旧站隔离,确认没有继续接受写入。
  2. 以新主为事实源重建或追平旧站。
  3. 比较日志位点、行数、金额、状态分布和业务对账。
  4. 恢复复制方向并观察稳定性。
  5. 小流量读验证,再开放低风险写。
  6. 分批回切,保留快速停止能力。
  7. 处理切换期间重复消息、缓存和任务状态。

直接反向复制可能把旧主分叉数据覆盖新事实。冲突数据应进入隔离、业务核对和可审计修复。

十六、JDK 8 Demo:单元路由与Epoch Fencing

java
public class UnitRoutingDemo {
    static String route(long tenantId) {
        return tenantId % 2 == 0 ? "unit-east" : "unit-south";
    }

    static final class FencedRecord {
        private long maxEpoch;
        private String value;

        synchronized boolean write(long epoch, String newValue) {
            if (epoch < maxEpoch) return false;
            maxEpoch = epoch;
            value = newValue;
            return true;
        }
    }

    public static void main(String[] args) {
        System.out.println("tenant 1001 -> " + route(1001));
        System.out.println("tenant 1002 -> " + route(1002));

        FencedRecord record = new FencedRecord();
        System.out.println("old primary epoch 7 -> " + record.write(7, "old"));
        System.out.println("new primary epoch 8 -> " + record.write(8, "new"));
        System.out.println("old primary retries -> " + record.write(7, "stale"));
        System.out.println("final=" + record.value + ", epoch=" + record.maxEpoch);
    }
}

取模只是教学路由,生产需要可扩容的一致映射、迁移状态和版本;内存Fencing也只是演示,真实检查必须在数据库、存储或外部副作用端原子执行。

十七、商业场景:医疗采集平台异地容灾

  1. 采集Agent先把原始报文和sequence写本地有界磁盘队列。
  2. Agent按医院归属unitCode连接主站,eventId保证重传幂等。
  3. 主站原始数据落库并写Outbox,异步复制到灾备站。
  4. 灾备站持续校验最后sequence、对象存储校验和与数据库位点。
  5. 主站故障时先停止旧站接入凭据和任务所有权,再提升灾备Epoch。
  6. Agent抖动重连到灾备站,从最后确认sequence续传。
  7. 恢复后按医院、批次和sequence对账,不把重复报文直接删除掩盖证据。

此设计的RPO不是一句“异步复制约几秒”,还取决于Agent本地队列是否持久、确认点在哪里、对象存储是否完成、Outbox是否提交和灾备是否可读取。

十八、生产Runbook

18.1 主站疑似故障

  1. 区分入口、应用、数据库、网络和整个Region故障。
  2. 检查是否只有部分运营商或地域访问失败。
  3. 记录数据库、MQ、CDC和对象存储位点。
  4. 判断旧主是否仍能写,切换前完成Fencing。
  5. 评估备站容量、证书、配置和依赖健康。

18.2 切换后订单查不到

  1. 按orderId解析/查询归属单元。
  2. 查看复制位点和切换时间,判断是否落在RPO缺口。
  3. 检查请求是否被错误路由到非归属单元。
  4. 查询Outbox、MQ和业务流水,不能只看缓存。
  5. 进入待确认和对账,不创建重复订单。

18.3 两边都出现写入

  1. 立即停止低Epoch或无法证明所有权的一侧。
  2. 保留双方日志、事务位点和业务流水。
  3. 按业务键识别冲突集合,不直接全量覆盖。
  4. 资金和医疗事实进入人工审计或确定性合并。
  5. 修复Fencing后才恢复写入。

18.4 DNS已切但仍有旧站流量

  1. 检查权威、递归、OS/JVM和应用缓存。
  2. 区分新连接和已有HTTP/2、gRPC、WebSocket连接。
  3. 在旧入口返回可识别迁移响应或主动Drain。
  4. 不依赖TTL保证瞬时全量切换。

十九、灾备演练验收

必须实际测量:

  • 从故障注入到检测、宣告、Fencing、切流各阶段耗时。
  • 恢复后最后可见业务流水,计算实际RPO。
  • 核心接口恢复到多少容量,计算实际RTO。
  • 复制缺口、重复消息、任务双跑和缓存重建。
  • 回切耗时与冲突数据处理。
  • 人员权限、Runbook命令、审计和沟通链路。

只做“备库能启动”不算灾备演练;必须从真实客户端入口完成一笔可核对业务,并完成回切闭环。

二十、常见误区

误区后果
两站部署应用就是双活数据仍单点,远端无法闭环
从库就是备份误删会复制到从库
DNS切换是瞬时的缓存和长连接继续访问旧站
两边都可写更可用无所有权和合并规则会脑裂
promote新主即可旧主未Fencing仍可写
异步复制一定只丢几秒大事务、网络和回放延迟扩大缺口
Redis锁能全球互斥各地域锁状态独立且复制不强一致
切换成功就结束还需数据核对、积压处理和回切
架构文档写RPO/RTO就达标必须用真实演练测量

二十一、面试回答主线

先区分HA、灾备、主备和多活;按业务定义RPO/RTO;用单元化说明流量和数据归属;解释复制确认点与读一致性;网络分区时先Fencing旧主再提升新主;切流还要考虑DNS缓存、长连接、MQ、缓存和任务;最后讲数据核对、渐进回切和灾备演练。

标准回答见多机房与异地多活面试题

二十二、关联知识点

本章小结

多活的核心是业务写所有权和故障状态机,而不是机器数量。真正可用的方案必须让流量路由、数据归属、复制确认、Fencing、消息、缓存、任务、切换和回切形成闭环,并通过真实业务演练证明RPO/RTO。