异地多活架构:跨地域高可用系统的设计与演进

多地多机房部署是互联网系统的必然发展方向。一个系统要走到这一步,必然要面对流量调配、数据拆分、网络延时、架构升级等一系列问题。本文从最简单的单机架构出发,沿着可用性不断提升的脉络,逐步推演出异地多活架构的完整面貌,并结合阿里单元化方案解析工业级落地实践。

为什么需要异地多活?

一个好的软件架构应当遵循三个核心原则:高性能、高可用、易扩展。其中,高可用通常用两个指标来衡量:

指标 含义
MTBF(Mean Time Between Failure) 两次故障的间隔时间,越长说明系统越稳定
MTTR(Mean Time To Repair) 故障恢复时间,越短说明对用户影响越小

可用性的计算公式为:

可用性(Availability)= MTBF / (MTBF + MTTR) × 100%

通常用"N 个 9"来描述系统的可用性等级:

可用性 年故障时间 日均故障时间
99%(2 个 9) 3.65 天 ~14.4 分钟
99.9%(3 个 9) 8.76 小时 ~86.4 秒
99.99%(4 个 9) 52.6 分钟 ~8.6 秒
99.999%(5 个 9) 5.26 分钟 ~0.86 秒

要达到 4 个 9 以上的可用性,平均每天的故障时间必须控制在 10 秒以内。每提升 1 个 9,都对系统设计提出更高的要求。

然而故障是不可避免的,主要来自三个方面:

  • 硬件故障:交换机、路由器、磁盘等硬件损坏
  • 软件问题:代码 Bug、配置错误、依赖服务异常
  • 不可抗力:地震、水灾、火灾、停电、光缆被挖断

历史上不乏惨痛的教训:

时间 事件 影响
2013.07 微信因市政施工导致光缆被挖断 宕机数小时
2015.05 杭州光纤被挖断 近 3 亿用户约 5 小时无法访问支付宝
2021.07 B站部分服务器机房故障 整站持续 3 小时无法访问
2021.10 富途证券机房电力闪断 用户 2 小时无法登录和交易

不同体量的系统关注的重点不同:体量小时关注用户增长,体量上来后关注性能体验,体量再大到一定规模后,可用性就变得尤为重要。对于全民级应用而言,再小概率的风险也不能忽视——这就是异地多活存在的根本原因。

部署架构的演进历程

第一阶段:单机架构

最简单的模型:客户端请求 → 业务应用 → 单机数据库 → 返回结果。

数据库单机部署,一旦遭遇意外,所有数据全部丢失。即使做了定期备份,也存在两个问题:

  • 恢复需要时间:停机恢复,时间取决于数据量
  • 数据不完整:备份存在时间差,不是最新数据

数据库越大,故障恢复时间越长,这种方案可能连 1 个 9 都达不到。

第二阶段:主从副本

在另一台机器上部署数据库从库(slave),与主库(master)保持实时同步。

优势 说明
数据完整性高 主从实时同步,数据差异极小
抗故障能力提升 主库异常时从库可切换为主库
读性能提升 业务可直接读从库,分担主库压力

提升系统可用性的关键就是冗余——担心一个实例故障就部署多个实例,担心一台机器宕机就部署多台机器。

第三阶段:同城灾备

机房级别的风险虽然概率小,但一旦发生影响巨大。应对方案就不能局限在一个机房内了——需要在同城再搭建一个机房,用专线网络连通。

冷备

B 机房只做数据备份,不提供实时服务,只在 A 机房故障时才启用。

  • 优点:数据有异地备份
  • 缺点:数据不完整、恢复期间业务不可用

热备

B 机房完整镜像 A 机房:接入层、业务应用、数据存储(从库)全部部署就位,处于待命状态。

A 机房故障时只需做两件事:

  1. B 机房所有从库提升为主库
  2. DNS 指向 B 机房,接入流量

热备相比冷备最大的优点是:随时可切换。

无论冷备还是热备,B 机房都处于备用状态,统称为同城灾备。它解决了机房级别的故障问题,可用性再次提升,但有一个隐患——B 机房从未经历过真实流量的考验,切换时不敢百分百保证能正常工作。

第四阶段:同城双活

让 B 机房也接入流量、实时提供服务,好处有二:

  1. 实时训练后备军:让 B 机房达到与 A 机房相同的"作战水平",随时可切换
  2. 分担流量压力:B 机房接入流量后,减轻 A 机房的负载

但 B 机房的存储是 A 机房的从库,默认不可写。解决方案是在业务应用层做读写分离改造

操作 路由策略
读请求 可读任意机房的存储
写请求 只允许写 A 机房(主库所在)

所有存储(MySQL、Redis 等)都需要区分读写请求,有一定的业务改造成本。A 机房为主机房,B 机房为从机房

两个机房部署在同城,物理距离近,专线网络延迟可接受。B 机房可以从 10% → 30% → 50% → 100% 逐步接入流量,持续验证其工作能力。

同城双活比灾备更进一步:B 机房实时接入流量,且能应对随时的故障切换,系统弹性大大增强。

但两个机房在物理上仍处于同一城市。如果整个城市发生自然灾害(如 2021 年河南水灾),两个机房依旧存在全局覆没的风险。

第五阶段:两地三中心

为了应对城市级别的灾难,需要在异地(通常建议距离 1000 公里以上)再部署一个机房。

  • A、B 机房在同一城市,同时提供服务(同城双活)
  • C 机房部署在异地,只做数据灾备

这就是两地三中心架构,常用于银行、金融、政企项目。但问题依旧:启用灾备机房需要时间,且启用后的服务不确定能否如期工作。

异地双活:跨越延迟的鸿沟

为什么"简单异地部署"行不通?

如果把同城双活的架构直接搬到异地(例如 A 在北京、B 在上海),会遇到一个致命问题——网络延迟

北京到上海约 1300 公里,即使光纤以光速传输,一个来回也需要近 10ms。加上路由器、交换机等设备,实际延迟可达 30ms 左右。更关键的是,远距离专线的质量远不如机房内网——延迟波动、丢包、甚至中断都是常态。

一个页面可能访问后端几十个 API,如果每次都跨机房访问,整个页面的响应延迟可能达到秒级——这是不可接受的。

虽然机房按同城双活的模型部署在了异地,但这本质上是一种伪异地双活

真正的异地双活:机房内闭环

既然跨机房延迟是客观存在的物理限制,核心思路就是尽量避免跨机房调用——每个机房的请求在本机房内完成闭环。

这意味着每个机房都需要拥有独立的读写能力:

改造项 说明
数据库双主 两个机房的数据库都是主库,支持本地读写
双向数据同步 任一机房写入的数据,自动同步到另一个机房
全量数据 两个机房都拥有全量数据,支持任意切换

数据双向同步

MySQL 本身支持双主架构和双向复制。但 Redis、消息队列(Kafka、RocketMQ 等)这些有状态服务并不原生支持,需要开发专用的数据同步中间件

数据同步中间件的核心作用:

北京机房写入 order=AAAAA → 中间件同步到上海
上海机房写入 order=BBBBB → 中间件同步到北京
最终:两个机房都有 order=AAAAA 和 order=BBBBB

使用中间件同步数据可以容忍专线的不稳定——专线出问题时中间件自动重试直到成功,达到数据最终一致性

数据冲突问题

两个机房都可写,如果修改的是同一条数据,就会发生冲突:

用户短时间内发起两个修改请求:
  → 请求 A 落在北京机房,修改 order=AAAAA(尚未同步到上海)
  → 请求 B 落在上海机房,修改 order=BBBBB(尚未同步到北京)
  → 两个机房以谁为准?

系统发生故障并不可怕,可怕的是数据发生错误,因为修正数据的成本极高。

解决数据冲突:路由分片

核心思想是:同一个用户的所有请求,只在一个机房内完成业务闭环,从根源上避免冲突。

需要在接入层之上部署路由层,根据规则将用户分流到不同机房。常见的分片策略有两种:

策略一:哈希分片

根据用户 userId 计算哈希值取模,从路由表中找到对应机房。

用户 0~700   → 北京机房
用户 701~999 → 上海机房

对于未登录用户:

  • 方案 A:全部路由到固定机房
  • 方案 B:根据设备 ID 进行哈希取模

策略二:地理位置分片

非常适合与地理位置密切相关的业务(打车、外卖等)。

北京、河北、内蒙古 → 北京机房
上海、浙江、江苏   → 上海机房

以外卖为例,商家、用户、骑手都在相同的地理范围内,天然适合按地域分片。

全局数据的特殊处理

有一类数据无法做分片——全局强一致数据,典型如商品库存。这类数据只能采用"写主机房、读从机房"的方案,无法真正双活。

这意味着在交易链路中,虽然全链路都做了机房内闭环,到了库存扣减这一步又回到了中心机房,单元化闭环被打破了。

一种解决思路是库存分摊:将一个商品的库存拆分到不同机房,每个机房独立扣减本地库存,再通过库存调拨程序在机房间进行库存共享和再平衡。

场景 方案
普通交易 库存分摊 + 库存调拨程序保证机房间库存共享
秒杀场景 各机房独立扣减,无需调拨(库存本就要被快速消耗完)

异地多活:从双活到 N 活

按照单元化的方式,每个机房可以部署在任意地区,随时扩展新机房,只需在最上层定义好分片规则。但随着机房数量增多,数据同步的复杂度急剧上升——每个机房写入数据后需要同步到所有其他机房,网状拓扑的复杂度为 O(N²)。

从网状到星状

业界的优化方案是将网状架构升级为星状:确立一个中心机房,所有数据同步都以中心机房为枢纽。

   ┌──────────┐
   │ 单元机房A │──┐
   └──────────┘  │
   ┌──────────┐  │  ┌──────────┐
   │ 单元机房B │──┼──│ 中心机房  │
   └──────────┘  │  └──────────┘
   ┌──────────┐  │
   │ 单元机房C │──┘
   └──────────┘
对比项 网状同步 星状同步
同步复杂度 O(N²),每增一个机房所有机房都需改造 O(N),只需同步到中心机房
扩展性 好,新机房只需和中心建立同步关系
中心依赖 中心机房稳定性要求高
容灾 任一机房可接管 中心故障时可提升任一机房为新中心

星状架构的优势

  • 一个机房写入数据只需同步到中心机房,中心再同步至其他机房
  • 不需要关心一共部署了多少机房,扩展新机房的成本极低
  • 中心机房故障时,可将任一单元机房提升为新中心,继续服务

至此,系统真正实现了异地多活——多个机房同时对外提供服务,任意机房故障可快速切换,系统具备极强的扩展能力。

阿里单元化实践

阿里在实施单元化时,根据业务特点采用了两种模式:

交易单元化 vs 导购单元化

对比维度 交易单元化 导购单元化
入口流量 入口清晰(商品详情→购物车→下单→支付) 入口分散,大促时增加各种场景和玩法
链路特征 为主 大部分是
数据库模式 WRITE 模式(本地读写,双向同步) COPY 模式(中心写入,单元只读)
单元化范围 全链路必须做单元化(对用户下单有直接影响) 仅 C 端服务做单元化,商家后台中心化部署
资源成本 较高(每个单元完整部署) 较低(商家后台等只部署在中心)

导购单元化采用 COPY 模式的原因:商家后台服务的可用性要求相对较低,故障恢复后继续操作即可,对大盘交易影响不大。中心化部署能大幅节省资源成本和维护成本,也能降低开发人员的开发成本。

单元化路由透传机制

单元化的核心在于路由信息的全链路透传——从接入层到最底层的数据层,每一层都需要能够正确识别和传递路由参数。

层次 路由机制
接入层 解析 HTTP 请求中的路由参数(cookie/header/body),路由到正确的应用 SLB
应用层 中间件从 HTTP 请求中提取路由参数保存到上下文,供后续 RPC 和消息使用
RPC 层 RPC 客户端从上下文取出路由参数,随 RPC 请求传递到远程 Provider
消息层 MQ 客户端发送消息时从上下文获取路由参数添加到消息属性,消费时还原到上下文
数据层 保证数据落库到正确单元的 DB,防止数据脏写

单元协同与单元保护

在单元化演进过程中,有两个关键问题需要解决:

单元协同:某些特定业务场景需要保证数据强一致性(如库存扣减),这类服务只能在中心单元提供服务。所有对中心服务的调用都会直接路由到中心单元完成。

单元保护:系统自上而下各层都要具备纠错保护能力,保证业务按单元化规则正确流转:

保护层 纠错机制
接入层纠偏 流量进入接入层后,通过路由参数判断归属单元,非本单元流量代理到正确的目标单元
RPC 纠偏 RPC Consumer 端根据请求的单元信息进行路由选址,错误流量会被重定向到正确单元
数据层保护 数据库层面的最后防线,防止数据写入错误的单元

异地多活落地的关键挑战

落地异地多活远不止架构设计,还需要在多个维度做好准备:

数据一致性保障

挑战 应对策略
同步延迟导致的数据不一致 接受最终一致性,业务层做好容错设计
数据冲突(双写同一条数据) 通过路由分片从源头避免,辅以冲突检测和仲裁机制
同步中断(专线故障) 中间件自动重试 + 断点续传,恢复后自动追数据
数据校验 定期对账程序比对两地数据,发现差异自动修复

机房切换策略

切换类型 触发条件 操作
计划内切换 机房维护、演练 逐步调整路由权重,平滑迁移流量
故障切换 机房故障 DNS 切换 + 路由规则调整,将故障机房流量转移到其他机房
回切 故障恢复 先同步恢复期间的增量数据,再逐步回切流量

业务分级与取舍

并非所有业务都需要做异地多活,需要根据业务重要程度进行分级:

级别 业务类型 多活策略
P0 核心交易链路(下单、支付) 必须做单元化,机房内完全闭环
P1 重要辅助(购物车、搜索) 做单元化部署,允许短时降级
P2 一般功能(商家后台、运营工具) 中心化部署,故障时暂时不可用
P3 非核心(日志、统计) 不做多活,故障后补数据

配套基础设施

异地多活的落地还依赖一系列配套设施:

  • 全局流量调度:DNS + HTTP DNS + 接入层路由,支持按规则精细分流
  • 数据同步中间件:覆盖 MySQL、Redis、MQ 等所有有状态服务
  • 统一配置中心:支持多机房配置的统一管理和快速下发
  • 全链路监控:跨机房的调用链追踪、数据同步延迟监控、一致性校验报告
  • 演练平台:定期进行故障演练,验证切换流程的有效性

架构演进全景对比

阶段 方案 机房数 可用性 核心特点 主要局限
1 单机架构 1 < 99% 最简单 单点故障,数据丢失
2 主从副本 1 ~99.9% 数据冗余 机房级故障无法应对
3 同城灾备 2(同城) ~99.95% 机房级冗余 备用机房未经验证
4 同城双活 2(同城) ~99.99% 双机房实时服务 无法应对城市级灾难
5 两地三中心 3(两城) ~99.99% 异地数据备份 灾备机房启用慢
6 异地双活 2(异地) ~99.99%+ 机房内闭环,双主同步 需要大量中间件和业务改造
7 异地多活 N(多地) ~99.999% 星状同步,任意扩展 实施复杂度高,需要强大的基础设施支撑

总结

异地多活的演进,本质上是一部用冗余换可用性的发展史。从中可以提炼出以下核心认知:

  1. 冗余是高可用的基石:从主从副本到多机房部署,每一次演进都是在更大的维度上做冗余
  2. 延迟是异地部署的核心矛盾:跨城网络延迟是客观物理限制,必须通过"机房内闭环"来规避
  3. 数据一致性是最大的技术挑战:双向同步、冲突避免、最终一致性保障,每一环都需要精心设计
  4. 路由分片是解决冲突的根本手段:通过哈希分片或地理分片,确保同一用户的请求在同一机房内闭环
  5. 星状拓扑是多活扩展的最优解:相比网状同步的 O(N²) 复杂度,星状拓扑将复杂度降为 O(N)
  6. 不是所有业务都需要多活:根据业务重要程度分级,P0 核心链路做完整单元化,非核心业务中心化部署节省成本
  7. 架构设计是技术与成本的平衡:异地多活需要路由层、数据同步中间件、监控体系、演练平台等大量基础设施支撑,没有足够的人力物力很难落地

好的架构不是一步到位的,而是随着业务体量的增长逐步演进的。理解每一步演进背后的驱动力和技术挑战,比直接套用某个方案更加重要。

加载导航中...

评论