Skip to content

07 · 多活容灾与全球化架构 ​

属于「架构师修炼」· 阶段六(坎 4~5 · 100 万~1000 万 QPS)· 多活不是"多部署一套",而是"数据怎么在多地写" 上一篇:06 分库分表与在线迁移双写 下一篇:08 缓存与 DB 一致性落地

这篇解决什么问题:面试里一说到高可用,很多人张口就是「同城双活 + 异地多活 + 单元化」,但一问「两地同时改同一个用户的数据,以谁为准」就断了。这一篇只讲真话:多活的 90% 失败都出在数据层,不在流量层——接入层切流是简单的,难的是让数据在多地写对。你会拿到:① 七种容灾形态的 RTO/RPO 量化对照;② 单元化解决什么、又留下什么全局难题;③ 数据同步与冲突处理的选型矩阵;④ 能直接抄的机房级演练剧本与成本清单。


一、先给结论:多活的难点全在数据层 ​

多活的技术动作只有两个:把流量调度过去(简单)、把数据在多地写对(困难)。 事故里 90% 不是「切流没切过去」,而是「切过去了,但数据错乱 / 读到旧数据 / 双向复制把好数据覆盖成坏数据」。

层次要解决的问题难度手段常见误区
接入层请求打到哪个机房★★DNS/GSLB、Anycast、客户端 SDK「多活 = 多地部署同一套服务」——无状态服务多部署理所当然,难的永远是有状态的数据
服务层每个机房是否自闭环★★★单元化、无状态化、依赖就地可用「机房建好就万无一失」——证书/密钥/风控没就地可用,切过去照样全挂
数据层多地怎么写同一份数据★★★★★单向主从、双向复制、binlog 同步、CRDT、单元固定写「多活 = 更高可用」——它同时新增了「跨机房不一致」这一故障源

1.1 可用性等级 → 架构手段 → 成本(面试直接引用) ​

可用性等级年停机架构形态典型 RTO / RPO相对成本
99.9%(1 个 9)~8.76 h单机房 + 主从 + 备份RTO 30 min~数小时 / RPO 分钟级×1
99.99%(2~3 个 9)~52.6 min同城双活(同城双写 + 存储层同步)RTO < 1 min / RPO ≈ 0×2~2.5
99.99%(3 个 9)~52.6 min两地三中心(生产 + 同城灾备 + 异地灾备)RTO 分钟~十分钟 / RPO 秒级×3~4
99.99%~99.999%(4~5 个 9)52.6 min~5.26 min异地多活(多地都可写)/ 单元化RTO ≈ 0(秒级切流)/ RPO ≈ 0,但有冲突×5~10
99.999%(5 个 9)~5.26 min单元化 + 异地多活 + 边缘接入(全球化)RTO ≈ 0 / RPO ≈ 0(单元内强一致,跨单元最终一致)×10+

⚠️ 这张表的价值不是「5 个 9 就要多活」,而是让你能说清**「每个 9 都是花钱买的,越往上边际成本越陡」。业务只要 99.9%,单机房 + 好备份就是正确**选择。


二、概念澄清:七种形态,RTO/RPO 差一个数量级 ​

2.1 形态对照表(背结论) ​

形态数据怎么算流量怎么走RTORPO成本适用
冷备定期备份到异地,平时不启动灾难时手工恢复小时~天级小时级×1.1内部系统、可容忍长停机
热备备机实时异步同步,平时不接流量手工/DNS 切10~30 min秒~分钟级×1.5中小业务兜底
主备(同城)主写、备同步(半同步/异步)主挂切备30 min~数小时(大头是决策时间)半同步 ≈ 0 / 异步秒级×2绝大多数创业公司的正确选择
同城双活同城两机房都接流量,存储层同步复制GSLB 分流< 1 min≈ 0×2~2.5同城双 AZ,性价比最高的 4 个 9
两地三中心生产 + 同城灾备(同步)+ 异地灾备(异步)灾难时切灾备分钟~十分钟秒级×3~4金融/政企合规
异地多活多地都能写,靠复制或路由收敛就近接入≈ 0≈ 0,但存在冲突×5~10国民级产品、跨地域延迟敏感
单元化(Set 化)用户按 uid 固定归属一个单元,单元内数据只有该单元写按 uid 路由到固定单元≈ 0≈ 0,冲突被架构消除×5~10超大规模 + 容灾 + 全球化三合一

本篇最重要的结论:异地多活的"数据冲突"不是被某种算法解决掉的,而是被"单元化"这种架构设计消除掉的——只要同一用户的写只落一个机房,就根本不存在写冲突。

2.2 RTO/RPO 是商业指标,不是技术指标 ​

决策链:业务问"停机 1 小时损失多少" → 推出可接受 RTO → 倒推备用系统要多热 → 成本 ×1/×2/×10 → 成本大于损失就退回上一档,成本小于损失就落地并演练验证。

A4/A5 级答法:RTO/RPO 从业务倒推,不从技术倒推。订单停机 1 小时损失 X,那 RTO 5 min / RPO 0 就划算,值得付同城双活的成本;日志分析停机一天无所谓,RPO 1 h、用备份就够,上多活是纯浪费。演进顺序是 备份异地 → 同城双活 → 单元化 → 异地多活,每一级的触发条件都是业务/合规,不是"别人都做了"。


三、单元化(Set 化):机房级故障 + 跨地域延迟的正解 ​

3.1 三种做法的本质差异 ​

做法数据归属冲突扩容方式故障影响面
同城双活(共写一份库)无归属靠存储层同步避免加机器受跨机房复制延迟影响
异地多活(双向复制)无归属必然冲突,要 LWW/CRDT加机器需冲突合并
单元化(Set 化)每份数据有唯一归属单元架构上消除建新单元 + 迁号段只影响该单元用户

定义(背下来):把系统按用户切成 N 个自闭环单元,每个单元内部有自己的接入层 + 服务 + 数据库 + 缓存 + MQ;同一用户永远路由到同一个单元,单元之间不共享写路径。它一次解决三件事:① 机房级故障(挂一个单元只影响 1/N 用户);② 跨地域延迟(就近落本地单元);③ 无限扩容(加容量 = 建新单元 + 迁号段,不动老单元)。

3.2 单元化拓扑 ​

看图两点:① 单元之间没有同步写,只有异步同步(用于全局数据聚合/灾备);② 路由表本身是全局数据,它挂了谁都找不到自己的单元,必须有本地缓存兜底。

3.3 路由表下发:三级兜底 + 一道安全闸门 ​

级别载体生效速度挂了怎么办
L1 内存快照进程内原子快照(秒级刷新)实时继续用旧快照服务
L2 配置中心etcd/Nacos 存号段规则(uid % 100 < 25 → unit-A)秒级读本地持久化快照
L3 内置规则进程内置一致性哈希/号段表即时与共识规则一致,允许极少量漂移

下发流程:路由服务生成版本 V → 写配置中心(etcd Put + Watch 推送)→ 各单元拉全量 → 双读比对 → 原子切换。必须带版本号 + 影响面闸门:一次变更超过 30% 槽位直接拒绝并告警,否则一次误下发就是全站流量错乱(代码见 3.6)。

3.4 单元自闭环:就地可用的清单 ​

  • 计算/存储/缓存/MQ:网关、服务、分片 DB、单元内 Redis 与 Kafka 全部单元内闭环;定时任务必须在单元内选主,否则多单元重复执行(10 篇);对象存储用区域级 bucket。
  • 配置与中间件:配置中心本地快照、注册中心与日志采集的本单元实例;最常见的坑是启动时强依赖远端配置中心——配置中心一挂,服务连启动都起不来。
  • 凭证(最容易被漏):证书、密钥、加密机、风控规则必须同步到每个单元——机器全都好了但证书取不到,切过去整个单元 TLS 全失败。

3.5 单元化之后仍然存在的全局数据(唯一"后门") ​

全局数据例子落地做法
路由映射表uid → 单元、号段分配强一致 KV(etcd/ZK)+ 各单元本地缓存
全局唯一 ID订单号、任务号号段模式:中心分配区间(每次 1 万),单元内本地发号(06 篇)
全局唯一约束手机号注册、用户名占用必须强一致:单库唯一索引或 etcd Txn CAS
全局库存/配额秒杀库存、活动预算库存分片(各单元一份 + 中心兜底)或全局预扣(00 篇 5.2)
配置与跨单元统计灰度名单、限流阈值;全站 DAU、总榜配置中心 + Watch(秒级延迟);统计走异步上报 + 聚合,不进写路径

面试追问「你这套单元化还有什么必须走全局?」——答不出这张表,说明只背了拓扑图。

3.6 Go 代码:uid → 单元路由 + 三级兜底 ​

go
package route

import (
	"context"
	"fmt"
	"log/slog"
	"sync"
	"sync/atomic"
)

// Unit:一个自闭环单元(含本单元 DB/缓存/MQ 入口)。
type Unit struct {
	Name    string // cn-east-1a
	DBShard string // order_db_3
}

// Table:号段 → 单元的映射快照,带版本号以便双读比对与回滚。
type Table struct {
	Version int64
	Slots   [100]string // 槽位 → 单元名
	units   map[string]Unit
}

func (t *Table) Pick(uid int64) Unit { return t.units[t.Slots[uid%100]] }

type ConfigCenter interface {
	GetRouteTable(ctx context.Context) (*Table, int64, error) // 全量 + revision
	WatchRoute(ctx context.Context) (<-chan int64, error)     // 变更通知
}

// Router:三级兜底 = 内存原子快照(L1) → 配置中心(L2) → 内置规则(L3)。
type Router struct {
	snap    atomic.Pointer[Table] // L1:热路径零锁、零网络
	builtin *Table                // L3:永不为空
	cli     ConfigCenter          // L2:可能不可用
	mu      sync.Mutex            // 只保护 Refresh,不在热路径
}

// Locate 是请求热路径:不访问配置中心、不加锁。
func (r *Router) Locate(uid int64) Unit { return r.snap.Load().Pick(uid) }

// Refresh:拉全量 → 影响面闸门 → 原子切换;失败一律保留旧快照(宁可旧不可无)。
func (r *Router) Refresh(ctx context.Context) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	next, rev, err := r.cli.GetRouteTable(ctx)
	if err != nil {
		slog.WarnContext(ctx, "route refresh failed, keep old snapshot", "err", err)
		return err
	}
	old := r.snap.Load()
	if old != nil && rev <= old.Version {
		return nil // 防旧版本覆盖新版本(revision 回退时保护)
	}
	if old != nil && diffSlots(old, next) > 30 { // 影响面闸门:变更超 30% 槽位
		return fmt.Errorf("route change too large, rev=%d", rev)
	}
	r.snap.Store(next)
	return nil
}

// 后台 Run:先 Store(builtin) 保证永不为空,再 Refresh 拉全量;
// 随后 Watch 循环 + 指数退避重连(上限 30s),收到事件即 Refresh,ctx.Done() 退出。

三个工程要点:① 热路径零依赖(Locate 只有一次 atomic.Load,配置中心抖动不影响请求);② 宁可旧不可无(刷新失败留旧快照,启动失败用内置规则);③ 拒绝大幅变更(30% 闸门是防配置误操作引发全站错乱的最后一道闸)。


四、数据同步方案:一致性强度决定 RPO ​

4.1 五种方案对照表(面试必背) ​

方案机制一致性延迟RPO代价与边界
单向主从(同城)主库写,binlog 同步/半同步到从库半同步=强,异步=弱同城 1~5 ms半同步 ≈ 0 / 异步秒级从库只读,不能承接写;跨城同步复制会把写延迟拉到几十 ms
双向复制两地互为主(双主/multi-source)弱(写完即返回)异步秒级秒级必然冲突:同行两地并发改 → 覆盖。必须有冲突策略或架构规避
基于 binlog 的异步同步单元 A binlog → MQ(Canal/Debezium)→ 单元 B 重放最终一致秒级(1~10 s)秒级可重放、可对账、可按表过滤;顺序性与幂等要自己做(05 篇/11 篇)
消息同步(业务层事件)本地库提交后发领域事件给对端最终一致秒~分钟级秒级同步字段可控,但需事件表 + 重试 + 对账,代码侵入大
分布式数据库多副本TiDB/PolarDB/OceanBase 用 Raft/Paxos 跨城多副本强一致(多数派确认)同城 2~10 ms;跨城 30~150 ms多数派内 ≈ 0写延迟随距离线性上升;跨城强一致让写 P99 上百毫秒,通常只在同城做强一致

4.2 数据流全景 ​

要点:用户数据不同步(各有归属),只同步全局表/汇总数据。一旦发现自己在同步用户订单表,就说明单元化没做干净,冲突迟早发生。

4.3 跨机房同步带宽估算(给数字的加分题) ​

text
① 同步量 = 峰值写 TPS × 单条变更字节 × binlog 放大系数(1.5~2)
   例:5 万 TPS × 1 KB × 1.8 ≈ 90 MB/s
② 单元出口 = 同步量 × 同步方向数(3 单元全互联时每单元 2 个方向)→ 90 × 2 = 180 MB/s
③ 冗余系数 1.5~2(binlog 积压追赶、DDL 全表变更、大促尖峰)→ 180 × 1.8 ≈ 324 MB/s ≈ 2.6 Gbps
④ 结论:单机房出方向需 ≥ 5 Gbps 专线(留 50% 余量);跨城专线 1 Gbps ≈ 1~3 万元/月(经验值)
   → 3 单元全互联仅专线 ≈ 10~30 万元/月,这笔账才是多活的真实成本

4.4 同步延迟导致「读到旧数据」——比冲突更常见 ​

最常见的不是冲突,而是写后读走了另一个单元:用户在 A 单元写完立刻查 B → 查到"没有订单"。解法是写后读粘滞(同 uid 短期强制回源归属单元,TTL 30~60 s);切流场景的判据见 6.2(位点差 < 100 MB 且延迟 < 3 s)。


五、数据冲突处理:四种策略 + 一种"消除冲突"的架构 ​

5.1 冲突分类(先分清,才能选对策略) ​

冲突类型例子能否用算法解决
同字段并发写 / 增量计数类并发改同一昵称;两地各 +1 点赞能(LWW;CRDT 计数器天然可合并)
业务约束类两地各扣一次余额,总额扣超不能,只能靠路由(单元化)或全局串行
唯一约束类两地同时注册同一手机号不能,只能强一致仲裁
删除 vs 更新A 删除、B 更新 → 数据"复活"需墓碑(tombstone)+ 版本比较

5.2 策略对照表 ​

策略原理一致性优点致命边界
LWW比时间戳/版本,大者胜最终一致实现最简单,无需协调依赖时钟;漂移会让较早的真实写入胜出,静默丢数据;不可用于资金/库存
版本向量每副本维护 {副本: 版本}最终一致能准确检测"真并发"只检测不解决;向量随副本数增长
CRDT可交换/可结合/幂等的结构(G-Counter、PN-Counter、OR-Set)强最终一致(可收敛)乱序到达也收敛,适合计数/集合仅适用于有数学性质的类型;元数据膨胀;删除语义复杂
业务规避(单元固定写)同 uid 的写永远只落一个单元无冲突根治,且延迟最优(就近写)需单元化改造;跨单元业务(转账、全局库存)依然存在
中心仲裁(强一致单点)关键写走 etcd Txn / 单库唯一索引强一致正确性最强吞吐受限(etcd 单集群写入几千 TPS),只适合"量小但必须对"的数据

5.3 LWW 的坑:时钟不是你想的那样 ​

坑后果量化
NTP 校时跳变时间戳回退,新数据被判旧而丢弃一次性校正可跳变数百 ms~秒级
时钟漂移两机时间缓慢分叉公网 NTP 约 几十~几百 ms/天,虚拟机更差
时区/闰秒;用 time.Now() 排序排序异常;同机多次调用也可能非单调必须统一 UTC;用 monotonic 时钟或 HLC/Lamport

正确做法:用 HLC(混合逻辑时钟),或直接取强一致组件的单调值——etcd 的 revision 天然单调递增,可直接当全局版本号 / fencing token(见 10 篇)。

5.4 结论:优先「消除冲突」,而不是「合并冲突」 ​


六、流量调度与切流 ​

6.1 三种调度方式对比 ​

方式原理生效速度粒度边界
DNS / GSLB按地域/运营商/健康检查返回不同 IP分钟级(受 TTL 与 LocalDNS 缓存影响,可能 10~30 min)机房级通用首选,但不能做秒级切流;TTL 30~60 s 已是极限
Anycast多地宣告同一 IP,BGP 就近选路秒级(BGP 收敛)网络级全球加速好;但连接可能被路由到不同机房,长连接需重建;回程不对称,排障难
客户端 SDK 路由SDK 内置规则 + 服务端下发,直连指定单元秒级~实时用户级最精准(可按 uid 路由),代价是要发版/维护 SDK,首次启动仍需 DNS 兜底

生产组合拳:GSLB(定地域)+ 客户端 SDK(定单元)+ 服务端路由兜底(uid → 单元)。

6.2 切流六步法(不丢单的核心) ​

步骤量化判定标准越线处置
① 预演B 单元 CPU 低于 50%、DB 连接低于 60%、预留 ≥ 1.5 倍承接量先扩容再说
② 停写入口返回 503 + Retry-After,不静默丢异步任务在途则等 30 s 排空
③ 追平位点差 < 100 MB 且延迟 < 3 s超时放弃切换(不带脏数据切)
④ 灰度每台阶错误率 < 0.1%、P99 < 基线 1.5 倍自动回滚上一台阶
⑤ 全量观察 30 min 无异常回滚
⑥ 对账自动补偿后差异条数 = 0人工介入,保留差异快照

「切流怎么保证不丢单」的标准答法:先停写 → 等追平 → 灰度切 → 再全量 → 最后对账。核心是「先让两边数据一致,再让流量过去」,绝不是「流量先打过去、指望复制追上」。

6.3 脑裂在跨机房的表现与防范 ​

表现根因防范
两地都认为自己是主,各自接受写专线抖动后两边都判定对方挂了多数派仲裁:只有含多数派(第三方故障域/同城第 3 机房)的一侧能当主
旧主继续写业务库旧主只是"失去资格",进程还活着、还连着自己的 DBfencing token:写请求带单调递增 epoch,存储端拒绝小 token(10 篇)
切换后"时空倒流"新主数据比旧主旧,已删数据复活切换前位点校验;关键表带版本号/墓碑,合并时丢弃旧版本
仲裁节点本身挂无法判多数派,切主停滞仲裁点放第三个独立故障域,心跳走多链路(专线 + 公网 VPN)

跨机房超时阈值的算法(加分):

text
判定"对方挂了"的阈值 T 必须:T > 2 × 专线抖动 P99 + 应用 GC STW 上限
例:抖动 P99 = 200 ms,STW P99 = 50 ms → T > 450 ms,取 1 s 作心跳失效阈值
T 太大 → 切换慢(RTO 变大);T 太小 → 抖动即误判脑裂
结论:跨机房 T 通常取 1~3 s(比同机房毫秒级大 1~2 个数量级)

七、容灾落地清单与演练剧本 ​

7.1 五项必做清单(缺一项,多活就不成立) ​

#项目具体要求验收方式
1数据备份异地全量 + 增量备份存到第三个地域(与生产、与副本都不同域),每季度真实恢复一次恢复演练报告里的 RPO 实测值
2配置与镜像同步镜像多 Region 复制;配置中心多单元部署 + 本地快照;manifest 版本一致断网拉镜像/拉配置成功
3依赖就地可用中间件、证书、密钥、风控规则在每个单元本地可用(见 3.4)单单元隔离运行 24 h 无外部依赖失败
4演练机制每季度 ≥ 1 次机房级演练,在生产环境做演练报告 + 改进项闭环
5值班与预案一线可执行的 runbook(谁决策、几步操作、判定标准、回滚命令)+ 授权链桌面推演 + 真实切流演练

7.2 机房级故障演练剧本(可直接抄) ​

#注入故障预期行为判定标准常见失败
1单机房整体断电流量 30 s 内切到备用单元全站成功率 > 99%,RTO < 60 s路由快照过期,客户端重试 3 次卡 9 s
2专线抖动(丢包 30%、+200 ms)同步积压但不切主,写仍在本单元完成单元内 P99 涨幅 < 20%,同步延迟 < 30 s 且不误切心跳阈值太小 → 误判脑裂、双主写入
3单元内 DB 主库宕机单元内主从切换(半同步,RPO ≈ 0)RTO < 30 s,切换后位点比对无丢数据从库延迟大导致丢数据;连接池未重连新主
4Redis/Kafka 挂一台副本提升,业务无感错误率 < 0.01%,消费无重复(幂等生效)客户端未重连;位点回退导致重复消费
5配置中心不可用 / 证书失效用本地快照继续运行;证书提前 30 天告警 + 热加载业务无感;无 TLS 失败重启强依赖配置中心 → 起不来(高危);证书硬编码进镜像
6路由表下发错误 / 脑裂演练大幅变更被闸门拒绝;旧主写被存储端拒绝拒绝生效、无流量错乱;对账差异 = 0无变更审核;存储端不校验 token → 数据静默分叉

黄金准则:演练不是「看系统多强」,而是「把预案变成肌肉记忆」。每次演练必须产出1 个 P0 改进项 + 完成人 + 截止时间,否则就是走过场。


八、成本与代价:10 倍成本换 10 倍可用性 ​

成本项量化说明
跨机房带宽3 单元全互联 ≈ 2.6 Gbps/单元出口 → 专线 10~30 万元/月见 4.3 估算公式
资源冗余每个单元都要按「能承接全量流量」规划集群利用率从 60~70% 掉到 20~30%,等效 ×3 资源
一致性与流程复杂度全局表、路由、对账、冲突处理系统全部新建;新增租户/加单元 = 改路由表 + 数据迁移 + 双跑验证新增 2~4 个服务;一次扩容 = 一次迁移项目,不是加机器
运维人力与排障多机房部署、演练、值班、证书治理人力 ×2~3;MTTR 可能反而变长(事故类型变成"数据静默错乱")

必须记住的一句话:多活是 10 倍成本换 10 倍可用性。业务量级和可用性要求没到,做多活就是拿复杂度换虚荣心——正确顺序是 备份异地 → 同城双活 → 单元化 → 异地多活,每一级都要有明确的触发条件(RTO/RPO 目标、合规要求、跨地域延迟指标)。


故障与一致性边界 ​

故障现象机制解释降级/兜底一致性边界
机房断电该单元用户全不可用,其余正常单元自闭环失效切到备用单元(先确认容量与数据归属)归属该单元的用户切换前无主,未同步写入可能丢失(RPO = 同步延迟)
跨机房专线抖动同步延迟上升,异地读到旧数据异步复制积压暂停切流决策,写仍在本单元完成跨单元数据秒级~分钟级不一致,不得依赖异地做实时读
同步延迟导致读到旧数据用户刚写完就看到旧值写后读走了另一单元/从库写后读粘滞(同 uid 回源,TTL 30~60 s)最终一致;不能用"读异地"证明写成功
切流后新机房容量不足切换瞬间大量超时、DB 连接打满未按全量预留资源;冷缓存回源放大 3~5 倍分批灰度 + 秒级回滚 + 本单元限流(12 篇)部分请求失败(可重试),不产生数据错误
双向复制冲突同一行被覆盖,或计数少算两地并发写,LWW 静默丢更新版本向量检测 + 对账补偿 + 改造为单元固定写强一致不可达,只能最终一致,必须接受"某次写入被丢弃"
路由表下发错误/过期用户被路由到错误单元,写入落到错误的库全局路由数据本身失效本地快照 + 大幅变更闸门 + 秒级回滚路由正确性是多活的元级前提,错了直接造成数据分叉
全局唯一约束冲突两地同时注册同一手机号唯一性本质需要全局串行中心化唯一索引 / etcd Txn CAS 仲裁必须强一致,不存在最终一致方案
仲裁节点故障无法判定多数派,切主停滞单点仲裁仲裁点放第三故障域 + 多链路心跳宁可短暂无主(保 Safety),不可双主

用法:被问「你的多活方案挂了怎么办」,永远按「现象 → 机制 → 降级 → 一致性边界」四段回答,一段都不少。


面试追问链 ​

  1. 「同城双活和异地多活的本质区别是什么?」 → 同城双活是"一份数据的两地副本",异地多活是"多地都能写"。同城靠存储层同步复制(1~5 ms)做到 RPO ≈ 0 且不产生写冲突,因为本质仍是"一个写点";异地多活的跨城延迟(30~150 ms)不允许同步复制,必须允许两地各自写,于是产生数据冲突。代价上同城 ×2,异地 ×5~10,且异地必须额外解决冲突、对账、路由。

  2. 「多活下数据冲突怎么办?」 → ① 先分类:同字段并发写、计数类、业务约束类、唯一约束类,前两类可合并,后两类不可合并;② 优先消除而非合并——单元化按 uid 固定归属,让同一用户的写只落一个单元(最推荐的答案);③ 无法消除的(全局库存、转账、唯一注册)用强一致仲裁(etcd Txn / 单库唯一索引);④ 展示类用 LWW,但要讲清 NTP 跳变/漂移的坑(用 HLC 或 etcd revision);⑤ 最后一定要说跨单元冲突靠对账收敛。

  3. 「切流怎么保证不丢单?」 → 六步:预演容量 → 停写 → 等同步追平 → 灰度切流 → 全量 → 对账。核心是「先让数据一致,再让流量过去」,绝不能"流量先打过去、指望复制追上"。判定标准:停写后入口返回可重试错误(不静默丢);追平判据是位点差 < 100 MB 且延迟 < 3 s;灰度 1%→5%→20%→50%→100%,每台阶观察 5 分钟、错误率 < 0.1%;任何一步越线立即回滚(配置开关,秒级生效)。

  4. 「RTO/RPO 怎么定?」 → 从业务倒推。先问"停机 1 小时损失多少"定 RTO,再问"丢 1 分钟数据什么后果"定 RPO。例:订单 RTO 5 min / RPO 0(值得上同城双活 + 半同步);日志分析 RTO 24 h / RPO 1 h(备份就够)。然后用演练实测验证而不是口头承诺,最后补成本:每提升一档,成本约翻 2 倍。

  5. 「为什么大多数公司不做异地多活?」 → ① 成本 ×5~10:专线 10~30 万元/月 + 集群利用率从 60% 降到 20~30% + 专职 SRE;② 复杂度:要新增路由、全局表、对账、冲突处理,排障 MTTR 反而变长,事故类型从"服务挂了"变成"数据静默错乱";③ 收益有限:绝大多数业务用「同城双活(4 个 9)+ 异地冷备」已超出需求。正确姿势是讲清触发条件:RTO 目标 < 1 min 且异地用户占比高,或有数据主权/合规硬要求。


自测清单 ​

  • [ ] 能默写「可用性等级 → 架构形态 → RTO/RPO → 成本倍数」对照表,并说清每个 9 的边际成本
  • [ ] 能区分冷备/热备/主备/同城双活/两地三中心/异地多活/单元化七种形态的定义与量化差异
  • [ ] 能画出单元化拓扑,并说出至少 4 类必须走全局的数据
  • [ ] 能给出跨机房同步带宽估算公式,并算出一次真实量级的专线需求与月成本
  • [ ] 能说出 LWW 依赖时钟的三个坑,并给出 HLC / etcd revision 的替代方案
  • [ ] 能背出切流六步及每步量化判定标准(追平阈值、灰度台阶、回滚条件)
  • [ ] 能独立设计一次机房级故障演练,并产出可闭环的 P0 改进项

下一篇:08 缓存与 DB 一致性落地 —— 多活解决"机房级不一致",下一篇解决每天都会遇到的"缓存与 DB 不一致"。

持续学习,持续构建。