容灾的目标不是让两边都亮着灯,而是在网络断开、时钟分叉和自动切换同时发生时,仍能证明谁有资格写。演练当天,两座机房都宣布自己是主——这份账单叫脑裂。
创业第 5 年 10 个月,「秒抢」累计用户约 7000 万,公司 295 人,研发 75 人。周四下午进行第一次机房级容灾演练:计划断开上海与杭州之间的业务链路,验证 5 分钟内切换。14:07,两边的「PRIMARY」指示灯同时亮起。
演练脚本原本只该切业务流量,却同时触发了两套独立的本地提升逻辑。上海看不见杭州,于是按本地 3 个节点中的 2 个投票成为主;杭州也把自己的 2 个节点临时重配成新成员集,并按 2/2 成为主。固定成员集被拆成两份,每份都给自己发了写入许可证。
两边都能服务用户,成功率甚至维持在 99% 以上;但同一场次的最后一批库存被各自扣减,同一用户的订单状态也在两边向不同方向推进。现在每多一秒正常流量,就多约 160 个需要人工或规则仲裁的业务键。
这不是「等网络恢复后自动同步」能解决的问题。复制只能传递两个已经发生的事实,不能决定哪个事实有权覆盖另一个。你必须先让至少一边失去写资格,冲突集合才会停止增长。
A会把分叉变成覆盖冲突。网络恢复只让差异相遇,不会产生业务仲裁规则。若复制按最后写入时间覆盖,时钟漂移还可能让较旧业务事实看起来更新。先围栏,再复制与合并。
B只围住了前门。DNS 有缓存与传播延迟,杭州本地消费者、定时任务和内部调用仍能直写。流量切换应该配合存储 fencing;没有下游拒绝,旧主仍可能在网络恢复后补写。
C会按每秒 160 个键扩大人工账单。按地域接受订单只有在单元之间数据与库存互不重叠时才安全;本次两边共享同一场次和用户空间,继续双写就是继续出售同一份权利。
D是这一步的正解:先恢复单一写入资格。入口停止新请求,存储层再用 token 或只读开关拒绝旧主写入,防止绕过入口的消费者和补偿任务继续修改;保全两边日志后,按固定成员配置确认上海持有合法 3/5 多数派。
多活最难的不是启动第二套系统,而是网络断开时仍维持单一裁决。对于共享同一份业务事实、要求强一致写入的固定成员集,核心约束是:
请注意第二行才是安全性的来源,而且它常被记错:固定 5 节点集群里并不是"只存在一个多数派"——任取 3 个节点都算多数派,一共 C(5,3) = 10 个合法子集,其中很多可以同时在线。让写入安全的不是"多数派唯一",而是任意两个多数派必然相交:那个交集里的诚实节点不会在同一任期为两个主投票,冲突历史因此无法各自拿到多数确认。
交集性质也只是必要条件,不是充分条件。要真正保证写安全,共识协议还必须做到:同一任期不重复投票、提交与日志匹配规则拒绝冲突历史、成员变更走安全的联合共识、以及旧主恢复后被任期检查或 fencing token 拒绝(本章 D1 就是这一条的现场版)。
第三行则要单独看:⌊(N−1)/2⌋ 描述的是可用性边界——还能凑出多数派、因而还能继续服务的故障上限。超过它时,正确的系统应当停止写入,此时安全性依然成立,只是不再可用。把它当成"安全性的故障上限"是本节最容易犯的错。代入节点数:
本次原始成员是 5 个:上海 3、杭州 2。链路断开后,上海 3/5 可以形成多数派,杭州 2/5 应停止强一致写。事故发生在杭州自动化没有保持原成员配置,而是把本地 2 节点重建成新的 2 节点集群;两个成员宇宙各自拥有多数,交集性质被配置变更绕开。
因此成员变更本身必须走共识,不能在网络分区时由两边独立「紧急重配」。若确需灾难恢复中重建集群,要先通过外部围栏撤销旧集群写资格,再授予新集群更高 fencing token。可用性可以暂时降低,写入权不能同时复制。
| 指标 | 定义 | 秒抢示例 | 容易混淆的点 |
|---|---|---|---|
| RTO | 从故障发生到业务恢复到目标服务水平的最长时间 | 核心查询 5 分钟恢复,出票 30 分钟追平 | 不是机器启动时间;要包括切流、校验和依赖恢复 |
| RPO | 恢复后可接受的数据丢失时间窗口 | 已付款订单 RPO=0;推荐点击可接受分钟级 | 不是复制延迟本身,而是业务愿意承担的丢失上限 |
假设 14:00 故障,14:04 恢复只读查询,14:09 恢复下单,那么核心写链路实际 RTO 是 9 分钟,不是实例在 14:02 启动所花的 2 分钟。若最后可恢复的订单日志停在 13:59:50,恢复点丢了 10 秒写入,实际 RPO 是 10 秒;对已付款订单,目标 RPO=0 意味着还要用支付账单与幂等日志补齐。
RTO 与 RPO 都要按业务能力分层。目录浏览可以先恢复并接受短时旧数据,库存扣减必须等写入资格和状态核验完成;把整站写成一个「RTO 5 分钟」,会让最快的页面掩盖最慢的资金链路。
| 档位 | 流量与数据 | 主要收益 | 主要账单 |
|---|---|---|---|
| 同城双活 | 两机房同时接流量,低延迟同步或共享主写 | 机房故障切换快,网络时延较可控 | 仍受城市级灾害与共享网络故障影响;双写权要明确 |
| 异地多活 | 多城市接流量,跨城复制,写策略按业务分级 | 覆盖城市级故障,用户可就近访问 | 延迟、复制窗口、冲突仲裁和运营成本显著增加 |
| 单元化 | 用户、库存与订单按单元归属,每单元尽量闭环 | 故障与热点限制在单元,跨城双写减少 | 路由、用户迁移、全局查询与单元间交易更复杂 |
单元化解决本次脑裂的前提,是同一业务键只归一个单元:上海用户与杭州用户若购买不同库存配额,分区时可以各自继续;若两边仍竞争同一场次最后 100 张票,地域标签没有消除共享写入事实。单元边界必须落到数据和配额,而不只是流量入口。
跨单元操作要显式承认代价。用户迁移需要冻结写入、复制状态、切路由再校验;全局订单搜索可以异步聚合,允许短时旧;支付与库存扣减则留在归属单元,避免一次交易同时要求两个城市达成同步事务。
第 11 章已经看到,锁过期后旧持有者可能醒来继续写。多活切换是同一问题的机房级版本。fencing token 为每次合法获得写权的主分配单调递增编号,下游记录自己见过的最大 token,并拒绝更小编号的写。
token 必须由具备单调性与持久性的权威发放,下游也必须真正校验;把 token 只写进日志,没有形成围栏。若某个旁路消费者不校验,它就是旧主重新进入系统的后门。
网络恢复后,两边会带着同一业务键的不同版本相遇。按 last_write_wins 合并看似简单,却依赖跨机房时钟可比较;附录 No.17 会说明时钟漂移如何让时间轴自相矛盾。订单、支付和库存更需要业务仲裁:
| 数据类型 | 冲突示例 | 仲裁原则 | 恢复动作 |
|---|---|---|---|
| 订单状态 | 一边 PAID,一边 CANCELLED | 按支付权威账与状态机版本判断,不按墙上时间 | 保留证据,生成补偿状态 |
| 库存 | 两边扣了同一份配额 | 以预先分配的单元配额或合法 token 为准 | 冻结受影响场次,逐笔履约与补偿 |
| 用户偏好 | 两边修改不同字段 | 可按字段合并,冲突字段再用版本策略 | 自动合并并记录审计 |
| 统计计数 | 两边各自累加 | 若操作可交换,可按增量合并 | 重算或使用可合并数据结构 |
第 3 章的复制延迟在同机房里制造了「写完暂时读不到」;跨机房后,延迟会扩到秒级甚至分钟级,网络分区还会让「暂时旧」升级为「双边各自写」。异地多活不是读写分离加一条更长的网线,而是必须重新定义哪些数据允许分叉、哪些写入需要多数派。
██‖██ 与常见宕机曲线不同:两边都活着、都忙、都认为成功。诊断钩子不是某条线掉下去,而是同一业务键出现两个并行版本。关卡四会把这个场景变成交互推演,让你在恢复速度与写入安全之间亲自做选择。
容灾不是地理距离越远越好。距离购买更大的故障隔离,也带来更长复制延迟、更难写入仲裁和更昂贵演练。每一个都是对的,每一个都有账单。
A · 同城双活:链路延迟低,强一致多数派与切流更容易达到分钟级 RTO。账单:城市级电力、网络或灾害仍可能同时影响两处;共享依赖会削弱隔离。适合先解决单机房故障、核心写强一致且团队演练经验有限的阶段。
B · 异地多活:覆盖城市级故障,用户可就近访问,区域容量可以互相接管。账单:跨城延迟、复制窗口、脑裂围栏、冲突仲裁和双边运营显著更复杂;不是每类数据都适合双写。适合业务已分级、RTO/RPO 清晰并能持续演练的团队。
C · 单元化异地多活:大多数交易在本单元闭环,故障和热点不易跨区域传播,共享强一致写减少。账单:单元路由、迁移、全局查询和跨单元业务需要新模型,容量配额可能碎片化。适合用户规模大、业务键可稳定归属的长期架构。
「秒抢」的选择:目标采用 C,但分两步走。先把同城双活的多数派、fencing、RTO/RPO 和演练做扎实;再按用户与场次配额建立上海、杭州单元,订单写在归属单元闭环,全局搜索异步聚合。没有通过关卡四式脑裂演练的能力,不进入跨城双写名单。
回放 14:07 的正确剧本——恢复服务排在恢复写入安全之后:
双层围栏。入口暂停该业务写入,存储侧拒绝杭州旧 epoch;冲突键停在 1,436,消费者与定时任务也无法绕过入口继续写。
确认合法成员集。按演练前 5 节点配置核对投票与 token,上海持有 3/5 多数派;撤销杭州临时成员配置,保全两边 WAL、订单事件和流量日志。
只读先恢复,写入慢放量。目录和历史订单先恢复;上海写入从 10% → 30% → 100% 放开,每级核对支付、库存与订单不变量,核心写 RTO 实测 12 分钟(14:07 告警 → 14:19 全量放开)。
仲裁分叉数据。按合法 token、支付账与库存凭证处理 1,436 个冲突键;自动合并低风险偏好,订单与库存逐笔补偿。完成后才恢复跨城复制。
跨城链路中断后,两边写入曲线都保持高位,同一订单键出现两个并行版本:██‖██。这是什么故障?
A。双边同时写、同键版本分叉,是脑裂的诊断钩子。D 很诱人,因为异步复制也会短时不一致;区别是正常架构仍有明确写入权,脑裂时两边都在批准同一业务空间的新事实。
固定成员的 5 节点集群,最多容忍多少个节点故障仍可形成多数派写入?
B。⌊(5−1)/2⌋ = 2,剩余 3 节点仍是多数派。C 会只剩 2/5,不能安全批准强一致写;若把这 2 个节点紧急重配成新集群,必须先围栏旧集群,否则会破坏交集。
14:00 故障,14:09 恢复核心写;恢复点只能还原到 13:59:50。实际 RTO 与数据窗口分别是多少?
A。RTO 看业务恢复到目标水平,用时 9 分钟;可恢复点落后故障时刻 10 秒,数据窗口是 10 秒。D 把机器启动当成业务恢复,忽略切流、依赖与校验;C 只有在补齐这 10 秒数据后才成立。
下游已经接受 token 42 的新主写入,旧主携带 token 41 在网络恢复后补写。下游应怎么做?
B。单调 token 代表写入资格代际,旧代写入必须被围栏。A 把网络迟到误当成更新;D 会把安全依赖到可能漂移的时钟,正是附录 No.17 要提醒的陷阱。