Featured image of post 异地多活:Java Web 微服务的高可用终极形态

异地多活:Java Web 微服务的高可用终极形态

异地多活:Java Web 微服务的高可用终极形态

Java Web 微服务系列 · 第 1 篇 · 异地多活 阅读时长:约 28 分钟 本文写于 2026 年 6 月

引子:机房炸了的那一夜

2018 年某日凌晨,杭州某互联网公司的主数据中心核心交换机突发故障,所有线上服务瞬间不可用。从用户端看:APP 加载失败、支付接口超时、客服电话被打爆、商家无法接单。技术团队用了 45 分钟完成故障切换,业务勉强恢复,但已经损失了数百万 GMV 和大量用户信任。

事后复盘:这次故障只是"机房级"——交换机坏了,换一台就好。

但如果是"城市级“灾难呢?地震、台风、洪水、光纤被施工队挖断、变电站爆炸……机房级灾备在这种场景下完全失效。你需要把整套系统分散到不同的城市,让任意一个城市"消失"时,其他城市依然能继续服务。

这就是异地多活

但异地多活不是"建几个机房 + 同步数据"那么简单。跨城市的光纤延迟 30-100ms,是机内调用的 60 倍,直接照搬同城双活的架构会让系统慢到用户无法接受。这就是为什么业界演化出"单元化"这套精巧的设计。

一、核心概念:把术语先掰开

在正式进入架构演化史之前,先把几个核心术语定义清楚——很多文章混用"灾备/双活/多活"的概念,导致读者越看越糊涂。

1.1 什么是异地多活

异地多活(Geo-Distributed Active-Active)指在地理上分散的多个机房同时对外提供服务(读写流量),任意机房故障时其他机房可快速接管全部业务。

三个关键词:

  • 地理分散:通常跨城市(北京-上海),距离 1000 公里以上
  • 同时对外服务:所有机房都在线,不是主备关系
  • 快速接管:故障时无需人工切换,其他机房自动承接流量

💡 原理:异地 vs 同城的本质区别

同城机房之间距离 < 100 公里,光纤往返延迟 < 1ms,可以"当一个机房用”。 异地机房之间距离 1000+ 公里,光纤往返延迟 30-100ms,不能再当一个机房用——同步要异步化、调用要避免跨机房、状态要"单元化"。

1.2 高可用的衡量标准

可用性有明确的量化公式:

1
可用性 = MTBF / (MTBF + MTTR) × 100%
  • MTBF(Mean Time Between Failures):平均故障间隔时间
  • MTTR(Mean Time To Repair):平均恢复时间

业内用"N 个 9"描述:

可用性年累计停机时间日均停机
99.9%(3 个 9)8.76 小时1.44 分钟
99.99%(4 个 9)52.6 分钟8.6 秒
99.999%(5 个 9)5.26 分钟0.86 秒

异地多活是实现 4 个 9 以上可用性的必经之路。仅靠单机或主从架构,3 个 9 已经是极限。

📌 实践:4 个 9 是什么概念

假设你每天用某 APP 下单 1000 万次:

  • 3 个 9:每天约有 14.4 分钟无法下单 → 约 1 万次失败
  • 4 个 9:每天约有 8.6 秒无法下单 → 约 100 次失败
  • 5 个 9:每天约有 0.86 秒无法下单 → 约 10 次失败

4 个 9 对应"一年内最多停机 52 分钟",这已经是绝大多数大型互联网公司的目标

1.3 两个关键指标:RTO 与 RPO

异地多活的效果要落到两个具体指标上:

  • RTO(Recovery Time Objective):故障到恢复的时间——系统能停多久
  • RPO(Recovery Point Objective):数据丢失的窗口——系统能丢多少数据

异地多活的理想目标:RTO ≈ 0(无感知切换)、RPO ≈ 0(不丢数据)。

方案RTORPO
单机不可恢复全部丢失
主从副本分钟级秒级
同城双活秒级接近 0
异地双活秒级秒级(异步同步)
异地多活秒级接近 0

📌 实践:RTO/RPO 是和老板谈预算的最佳语言

别只说"系统要做异地多活",要说"业务每年可接受的停机时间是 X 分钟,每次故障可接受的数据丢失是 Y 条"。这两个数字决定了架构选型。

老板们听不懂"高可用"和"灾备",但听得懂"我一年最多能接受亏多少钱"。

二、架构演化史:从单机到异地多活

从单机到异地多活,业界走过了一段漫长的路。核心思想始终是"冗余"——用多份资源换取可用性。

2.1 七阶段演进时间线

2.2 各阶段对比

阶段抵御风险恢复速度复杂度跨机房延迟典型应用
单机-最低-内部工具、Demo
主从副本服务器级-小型 SaaS
同城灾备(冷备)机房级慢(小时级)1-5ms传统企业 IT
同城灾备(热备)机房级较快(分钟级)1-5ms中型电商
同城双活机房级极快(秒级)1-5ms大型电商
两地三中心城市级较慢(人工决策)较高30-100ms银行、金融
异地双活城市级极快(秒级)30-100ms大型互联网
异地多活城市级 + 规模压力极快(秒级)最高30-100ms阿里、美团、字节

2.3 关键阶段详解

单机:一台机器打天下

所有服务和数据都在一台机器上。任何硬件故障 = 业务中断。这在 2000 年前的很多小公司很常见,今天只剩 demo 项目还在用。

主从副本:最基础的冗余

数据库主库 + 从库,主库写、从库读。服务器硬件故障时,从库可提升为主库。

  • 优点:实现简单,MySQL 原生支持
  • 缺点:故障切换需人工/脚本介入;切换延迟通常分钟级
  • 典型场景:内部 OA、测试环境

同城灾备:机房级容灾

同城(< 100 公里)再建一个机房,实时同步数据。

  • 冷备:仅做数据备份,不提供服务。故障时需要冷启动所有服务,小时级恢复
  • 热备:实时同步数据 + 提前部署好所有服务,故障时可手动切流量,分钟级恢复

💡 原理:冷备 vs 热备的成本差

冷备只备份数据,成本是"一份存储 + 一点点带宽"。 热备要部署完整服务栈,成本是"完整机房 + 完整运维"。 很多公司为了省钱做冷备,结果故障时恢复 4 小时,损失 200 万——省小钱亏大钱

同城双活:机房都承担流量

两个机房都接入流量,但写流量只走主机房,读可走任意机房。逻辑上仍视为一个机房。

  • 优点:资源利用率高,机房级故障可自动切换
  • 缺点:写流量仍是单点(主机房故障时切换仍需决策)

两地三中心:跨城市的折中

2 个城市 + 3 个机房:同城 2 机房双活 + 异地 1 机房灾备。常见于银行、金融、政企。

  • 优点:兼顾机房级和城市级容灾
  • 缺点:异地机房仍是冷/热备状态,资源浪费严重

🎯 避坑点:两地三中心 ≠ 异地双活

很多文章把"两地三中心"和"异地双活"混为一谈,其实完全不同:

  • 两地三中心:异地机房是灾备(平时不接流量),故障时切
  • 异地双活:异地机房平时也接流量,故障时对方接管

监管要求"两地三中心"是最低标准,异地双活是更高标准

异地双活:迈向真正的多活

两个城市各建一个机房,都承担读写流量。任意城市故障时,另一个城市接管全部业务。

  • 优点:真正解决城市级故障
  • 难点:跨机房数据同步、调用延迟、状态一致性

异地多活:终态

3 个及以上城市部署机房,每个机房都是"主"。新增城市时无需重构同步链路。

  • 优点:容量大、地域覆盖广、单机房故障影响小
  • 难点:复杂度极高,运维成本巨大

💡 原理:演进的内在动力

业务量 ↑ → 故障域要求 ↑ → 单机房容量上限突破 → 必须分散到多机房 业务连续性要求 ↑ → 城市级故障不可接受 → 必须跨城市 资金可承受 → 多活成为合理选择

演进的本质是"成本 ↔ 可用性"的权衡,不是技术炫技。

三、为什么需要异地多活

3.1 城市级故障是真实存在的

来自云厂商的公开故障记录(不完全统计):

  • 2019 年 3 月:阿里云华北 2 地域可用区 C 因光缆中断服务异常
  • 2022 年 12 月:腾讯云广州区域部分服务控制台异常
  • 2023 年 8 月:多个云厂商海外 Region 因海底光缆故障网络抖动

自然灾害(地震、洪水、台风)、运营商故障(光缆挖断)、人为误操作(配置错误、误删数据)——城市级故障每年都在发生

📌 实践:业务连续性分级

业务系统应按中断影响分级,决定采用哪种灾备方案:

等级业务类型RTORPO推荐方案
P0核心交易< 5 分钟< 1 分钟异地多活
P1重要业务< 30 分钟< 5 分钟同城双活 + 异地灾备
P2一般业务< 数小时< 1 小时同城灾备(热备)
P3后台系统< 1 天< 1 天主从副本即可

3.2 业务中断的代价

故障成本远不止"少卖几单"那么简单:

  • 直接损失:交易系统每分钟损失数百万 GMV(双 11 期间可达千万级)
  • 品牌损失:用户信任度下降,监管关注,媒体曝光
  • 用户流失:一次大故障可能流失 5-10% 活跃用户
  • 员工成本:全员熬夜处理,工时损失巨大
  • 法律风险:金融、医疗行业有合规要求,故障可能引发监管处罚

🛑 误区警示

我们小公司不会遇到城市级故障"——这是最常见的误判。 即便你不用云厂商,你的云厂商会。2023 年某中型电商因云厂商 Region 故障停业 6 小时,直接损失过千万。

3.3 合规与出海

  • 强监管行业:金融、证券、医疗——监管明确要求"两地三中心”
  • 出海业务:海外 Region 故障率高于国内,且跨国专线更脆弱
  • 上市/融资需求:投资人尽调会问"你们的灾备能力如何"

异地多活已经从"加分项"变成"必选项"。

四、具体怎么操作

这是本文最核心的部分。异地多活的实施分五大块:存储双向同步 → 单元化 → 兜底机制 → 全局数据例外 → 故障演练与监控

4.1 存储层:双向同步

异地多活的前提是两个机房都能写。这要求数据层做双向同步。

主流存储的双向同步方案

存储双向同步方案工具 / 中间件同步延迟冲突解决
MySQL双主模式 + binlog 同步MySQL 原生双主、阿里 Canal、Otter秒级字段合并 / 业务回避
Redis主从双向 + 冲突解决RedisShake、自研中间件毫秒级LWW / 业务回避
MongoDBReplica Set + OplogMongoShake、自研秒级版本号 / 业务回避
消息队列双向 Topic + 幂等消费Kafka MirrorMaker、RocketMQ毫秒级幂等去重
ElasticsearchCCR 双向ES CCR秒级版本号

MySQL 双主同步的两个坑

坑 1:自增 ID 冲突 双主都从 1 开始自增会重复写入。解决方案:

  • 奇偶分片:A 库 auto_increment_increment=2, offset=1(1, 3, 5…);B 库 offset=2(2, 4, 6…)
  • 全局序列:用 Snowflake(机房号段区分)或 Redis incr 生成

坑 2:循环同步 A 同步给 B,B 同步给 A,会形成死循环。

🎯 避坑点:循环同步

MySQL 双主同步必须配置 replicate-wild-ignore-table 过滤掉"自己产生的 binlog",否则会导致:

  1. A 写一条记录 → 同步给 B
  2. B 接收到后写入自己的 binlog → 又同步给 A
  3. 无限循环,主从延迟飙升

解决方案:使用 GTID 模式(MySQL 5.7+),每个事务有全局唯一 ID,已同步过的不再同步。

阿里 Canal 实战

Canal 是阿里开源的 MySQL binlog 订阅组件,常用于异地多活的增量同步:

1
MySQL Master → Canal Server(伪装成从库)→ Kafka → 消费端写入目标库

Canal 的优势:

  • 伪装成 MySQL Slave,对主库零侵入
  • 支持多种下游(Kafka、RocketMQ、RabbitMQ)
  • 配合 Otter 可以做到全自动化同步 + 反向校验

Canal 部署架构

完整的 Canal 异地同步架构通常包含三层:

  1. Canal Server 集群:每个机房部署 3-5 个 Canal Server 实例,负责订阅本机房 MySQL 的 binlog
  2. Kafka 集群:作为 Canal Server 和消费端之间的消息缓冲,应对消费端故障或回溯
  3. 消费端集群:订阅 Kafka 消息,按顺序应用到目标机房

关键参数:

  • canal.instance.parser.parallel:是否并行解析 binlog(默认 true)
  • canal.instance.tsdb.enable:是否启用时间戳库(用于断点续传,必开
  • kafka.batch.size:批量发送大小(建议 1000-5000)

📌 实践:增量同步 + 全量校验

异地多活的数据同步不能只做增量。还需要定期全量校验:

  • 增量同步 Canal 负责(秒级延迟)
  • 全量校验用 pt-table-checksum(小时级发现不一致)
  • 不一致数据用 pt-table-sync 修复

增量只能保证"目前一致",全量校验才能保证"历史一致"。

建议校验频率:核心表每小时一次,非核心表每天一次。漏掉校验的那一天,可能就是数据漂移的开始

4.2 数据冲突:必须解决的根本问题

当同一数据被两个机房同时修改时,无法判定先后

举例:用户在机房 A 修改了自己的昵称"张三",同一秒在机房 B 登录并改成了"李四"。两个机房互相同步时,谁覆盖谁?

两种解决思路:

方案 1:中间件自动合并 依赖时钟排序,最后写入获胜(LWW, Last-Write-Wins)。问题是分布式时钟不准——NTP 同步误差可达 100ms,跨城市误差更大。

方案 2:从源头避免冲突——单元化(业界主流)

💡 原理:避免冲突 > 解决冲突

分布式系统有一句名言:"不要试图用软件解决所有问题,要用业务设计避免问题"。 单元化就是这种思路——从架构上保证同一数据只在一个机房被修改,冲突从根本上不存在。

4.3 单元化:异地多活的核心

在接入层之上加路由层,按规则把用户分流到固定机房,同一用户的所有业务在同一机房内闭环,根除跨机房调用。

三种分片规则

分片方式适用场景优点缺点
按业务类型业务边界清晰实施简单不适合通用业务
直接哈希分片通用业务(电商、社交)均衡分布机房扩容需数据迁移
按地理位置O2O(外卖、打车)用户延迟低用户跨地区会"漂移"

直接哈希分片示例

假设机房 A、B:

  • 用户 ID % 2 == 0 → 机房 A
  • 用户 ID % 2 == 1 → 机房 B

同一用户的所有读写都路由到固定机房,业务闭环。两个机房独立运行,不互相调用。

单元化的 3 大原则

  1. 入口唯一:每个用户从入口就被打上"属于哪个机房"的标签,后续所有调用都基于这个标签
  2. 数据本地化:用户数据只存在自己所属的机房,跨机房读通过异步同步
  3. 调用收敛:禁止跨机房 RPC 调用,必须通过数据复制或中转层

单元化的代价

单元化不是免费的:

  • 业务改造:所有 RPC 调用要标注"是否跨机房",跨机房调用要被显式禁止或转发
  • 数据迁移:机房扩容时(如 2 机房变 3 机房)要重新计算哈希,迁移用户
  • 全链路追踪:要能监控每个请求走的是哪个机房,否则出问题无法排查
  • 全局服务特殊处理:支付、库存、配置等"全局服务"无法单元化,需要单独设计

4.4 兜底机制

路由规则理论上保证用户不漂移,但实际会有边界情况:

  • 跨机房调用(如支付、库存等全局服务)
  • 机房切换时的请求路由
  • 用户的"漂移"(如出差、出国)

解决方案:在写存储时检测数据归属

  • 写入前判断"这条数据属于哪个机房"
  • 如果不属于本机房,报错或转发到正确机房
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 伪代码:写存储前的归属检测
public void saveOrder(Long userId, Order order) {
    String targetDC = router.getDCByUserId(userId);  // 计算用户归属机房
    String currentDC = DataCenterContext.current();  // 当前机房
    if (!targetDC.equals(currentDC)) {
        // 通过 RPC 转发到正确机房
        rpcClient.to(targetDC).saveOrder(userId, order);
        return;
    }
    // 本机房写库
    orderRepository.save(order);
}

📌 实践:兜底是单元化的"安全网"

单元化路由 + 兜底检测 = 双保险。

  • 路由层:99.99% 的请求路由到正确机房
  • 兜底层:剩下 0.01% 的异常请求被拦截或转发

缺一不可。没有兜底的单元化,出一次事故就要全量排查

4.5 全局数据例外

不是所有数据都要双活

强一致数据(系统配置、商品库存、汇率、订单号生成器)仍采用"写主机房、读从机房“方案:

  • 这类数据写入频率低,强一致要求高
  • 跨机房写入延迟反而更糟

策略:

数据类型同步方案一致性保障典型工具
配置中心单点写 + 多点读ZK/Consul/Nacos 推 + 定时拉Nacos
全局 ID机房号段隔离Snowflake 41 位时间戳 + 5 位机房号美团 Leaf
库存系统单点写 + 异步同步异步消息 + 失败重试RocketMQ
订单流水双活单元化同用户订单落在同机房-
支付清算单点写 + 多点读T+1 同步 + 实时对账-

🛑 误区警示

盲目追求"全部双活"是常见错误

  • 全局数据双活得不偿失:成本 3-5 倍,但收益仅"机房级容灾”
  • 同城双活 + 异地灾备已能满足 P1 业务
  • 只对 P0 业务做异地多活即可

4.6 故障演练:必须做的事

异地多活的最大风险是"平时一切正常,故障时不会切"。

Chaos Engineering(混沌工程):主动制造故障,验证切换流程。

演练清单

  • 单机房断网(拔网线模拟)
  • 单机房电源中断
  • 数据库主从切换
  • 跨机房专线中断
  • DNS 污染
  • CDN 故障
  • 整机房故障(最严酷场景)

演练频率

  • 新机房上线前:必须通过完整演练
  • 日常:每月一次小规模演练
  • 大版本上线前:必须做一次全链路演练
  • 年度:至少一次"整机房故障"演练

🎯 避坑点:演练 ≠ 文档

很多团队写了"故障切换手册"就以为高枕无忧。手册没演练过 = 不存在。 真正打过几次"实战",你才知道手册哪些步骤是错的、哪些是过时的、哪些根本没有可执行性。

某互联网公司在 2020 年做第一次真实整机房演练时发现,切换脚本里的目标机房 ID 写错了 3 年没人发现——因为从没真正切过。

Netflix Chaos Monkey 的启发

Netflix 是混沌工程的鼻祖,开源了 Chaos Monkey 系列工具:

  • Chaos Monkey:随机杀掉生产环境的 EC2 实例
  • Latency Monkey:注入网络延迟
  • Conformity Monkey:找不符合最佳实践的实例并关掉

Netflix 的哲学:"在生产环境演练,而不是在测试环境"。只有真实流量下的故障切换才有意义

4.7 监控告警:异地多活的"心跳"

异地多活的监控比传统架构复杂得多——要同时监控"业务指标“和”基础设施指标"。

三层监控

层级监控指标告警阈值工具
基础设施CPU、内存、磁盘、网络、专线延迟标准运维阈值Prometheus + Grafana
中间件DB 主从延迟、MQ 积压、缓存命中率按业务定制Prometheus + 自研 Exporter
业务侧机房分流比例、跨机房调用比例、错误率偏离基线 > 5%SkyWalking + 自研监控

拨测:业务侧的"心跳"

从公网模拟用户请求,每分钟拨测各机房关键接口

  • 拨测失败率 > 1% → 告警
  • 拨测延迟 > 基线 2 倍 → 告警

📌 实践:拨测是异地多活的"生命线"

基础设施监控正常 ≠ 业务可用。 唯一可靠的判断是"从公网调用业务接口是否成功"。

拨测要在公网多个地域部署,不能只部署在机房内—— 否则你连"机房和公网之间的网络"出故障都不知道。

推荐拨测点:北京、上海、广州、深圳 + 海外 1-2 个城市(AWS 东京/新加坡)。

业务侧的关键指标

除了技术指标,异地多活还有几个业务侧专属指标必须监控:

  • 机房分流比例:A 机房 50%、B 机房 50% 是健康态。漂移到 70/30 说明有 bug
  • 跨机房调用比例:理论上接近 0。突然飙升说明单元化规则被破坏
  • 异地同步延迟:MySQL 同步延迟、消息同步延迟。超过 5 秒要告警
  • 用户漂移率:同一用户多次访问落到不同机房的比例。> 1% 就要排查

五、真实案例

5.1 阿里:三地五中心

阿里在 2015 年完成"三地五中心“架构:

  • 3 个城市:杭州、北京、深圳
  • 5 个机房:每城市 1-2 个
  • 每个单元都独立支撑全量业务

关键技术:

  • 单元化 + 异地多活
  • OceanBase 分布式数据库(保障数据一致性)
  • 异地专线 + 自研流量调度系统

阿里的核心思想:”异地多活不是分布式系统的子集,而是全新的架构范式"——业务设计要按"单元"组织,数据存储要按"单元"分布,流量调度要按"单元"分发。

5.2 美团:两地三中心

美团采用"两地三中心“架构(北京 + 上海):

  • 北京 2 机房 + 上海 1 机房
  • 单元化按用户 ID 分片
  • 金融级业务做异地双活,O2O 业务做同城双活

美团的特点:

  • 业务分级:金融级(支付、贷款)= 异地双活;O2O(外卖、到店)= 同城双活
  • 自研中间件:自研 OCTO 服务框架 + 自研同步组件
  • 业务容错:单元化失败时,降级到同城双活 而不是直接挂掉

5.3 字节跳动:单元化 + 异地

字节的多活架构特点:

  • 国内多个 Region,海外多个 Region
  • 单元化粒度更细(按服务级别)
  • 自研 Service Mesh 治理跨机房调用

字节的核心创新:

  • Service Mesh 跨机房治理:跨机房调用通过 Mesh 自动处理(限流、重试、降级)
  • 全链路单元化:从入口网关到数据库,全部带"机房标签”
  • 多语言统一:Go/Java/Python 服务都能融入单元化框架

💡 原理:单元化是异地多活的"第一性原理"

不论叫"三地五中心"还是"两地三中心",核心都是:

  1. 业务单元化划分(让用户绑死在某个机房)
  2. 存储层双向同步(让机房之间数据可写)
  3. 最上层分片逻辑(决定请求去哪)

任何异地多活方案,缺一不可。

六、总结

6.1 异地多活的 3 大核心要素

  1. 业务单元化划分——让同一用户的所有请求在同一机房完成
  2. 存储层双向同步——让两个机房都能写
  3. 最上层分片逻辑——决定请求去哪

6.2 实施成本

异地多活不是"装个软件就完事",需要配套建设:

  • 业务层:微服务部署、依赖拆分、SDK、Web 框架
  • 基础设施:服务发现、流量调度、CI/CD、同步中间件
  • 数据保障:数据分类、一致性保障、机房切换一致性
  • 运维:监控体系、异常处理、故障演练

6.3 何时该上异地多活

业务特征推荐方案
用户量 < 100 万同城双活
强监管(金融)两地三中心
O2O 业务同城双活 + 异地灾备
业务中断可接受数小时同城灾备(热备)

异地多活的 ROI 拐点在"日活 1000 万 + 业务连续性要求 4 个 9"。低于这个规模做了也是浪费。

6.4 异地多活的常见陷阱

🛑 误区警示:这些坑别踩

  1. 盲目上异地双活:业务量没到,成本先到
  2. 忽视单元化改造:直接照搬同城双活,性能差 60 倍
  3. 不做故障演练:手册写了等于零,必须真打实战
  4. 过度追求强一致:所有数据都双活,成本爆炸
  5. 忽视运维成本:复杂度是同城的 5 倍,团队要相应扩

异地多活是"用钱换命"的工程,不是技术炫技

📌 工程实践:异地多活的成本对比

成本对比(同业务量级):

  • 单机:1x
  • 同城双活:2-3x
  • 异地双活:5-8x
  • 异地多活:10-15x

决策前先问:

  1. 业务真的需要 4 个 9 吗?
  2. 城市级故障的发生概率是多少?
  3. 一次大故障的最大损失是多少?
  4. 异地多活的成本,多久能通过"避免的损失"回本?

6.5 系列预告

这是 Java Web 微服务系列 的开篇。后续计划覆盖:

  • 服务治理:服务发现、配置中心、熔断降级
  • 网关与限流:Spring Cloud Gateway、Sentinel
  • 分布式事务:Seata、Saga、TCC 模式对比
  • 链路追踪:SkyWalking、Jaeger
  • Spring Cloud Alibaba 实战:Nacos + Sentinel + Seata 三件套

七、异地多活实施清单

如果你决定启动异地多活项目,按以下清单逐项检查,可以少踩很多坑。

7.1 业务层 Checklist

  • 业务分级:明确 P0/P1/P2/P3 业务,确定哪些要异地多活
  • 用户标识:确定分片键(用户 ID / 租户 ID / 业务 ID)
  • 单元化拆分:按分片键拆分服务调用链,画出"业务-数据-机房"映射图
  • 全局服务识别:识别无法单元化的服务(支付、库存、配置中心),单独设计
  • 跨机房调用禁止:通过 Service Mesh 或代码规范禁止跨机房 RPC
  • 数据归属检测:所有写操作前检测数据归属机房

7.2 数据层 Checklist

  • 数据库选型:MySQL 用双主 + GTID;Redis 用 RedisShake;MQ 用 MirrorMaker
  • 同步中间件:部署 Canal + Kafka 集群,保证 binlog 不丢失
  • 全量校验:pt-table-checksum 定期跑,pt-table-sync 修复
  • ID 方案:改用 Snowflake / Leaf 分布式 ID,不用自增
  • 数据分类:明确"双活数据"和"全局数据"的边界
  • 冲突解决策略:每个表写明冲突解决方式(LWW / 业务回避 / 人工合并)

7.3 流量层 Checklist

  • 入口打标:在负载均衡 / 网关层就打上"用户属于哪个机房"的标签
  • 路由层高可用:路由层本身要异地多活(最关键,不能单点)
  • 健康检查:实时探测机房健康度,自动剔除故障机房
  • DNS 切流:准备好 DNS 切流脚本,故障时一键切换
  • 灰度切流:先切 1% 流量观察,再切 10%、50%、100%

7.4 运维层 Checklist

  • 监控大盘:业务侧 + 中间件 + 基础设施三层监控
  • 拨测系统:公网多地域拨测,覆盖核心接口
  • 告警分级:P0 故障 5 分钟内电话告警到负责人
  • 故障演练:每月小演练、季度大演练、年度整机房演练
  • Runbook:每个故障场景有对应 Runbook,且 至少演练过一次
  • 复盘机制:每次故障 24 小时内复盘,输出 Action Item 跟踪

7.5 团队层 Checklist

  • 架构师:至少 2 名资深架构师能讲清楚单元化路由
  • DBA:有异地多活 MySQL 双主运维经验
  • SRE:能做混沌工程演练
  • 值班:7×24 小时值班,异地多活的故障不分昼夜
  • 跨团队协作:业务 / 后端 / 数据 / SRE / 安全 团队对齐

🎯 避坑点:清单的 80/20 法则

上面 30+ 项检查,80% 的项目栽在前 10 项

  1. 业务分级不清晰
  2. 全局服务识别不全
  3. 跨机房调用没禁住
  4. ID 方案没换
  5. 入口没打标
  6. 路由层单点
  7. 没有全量校验
  8. 没做故障演练
  9. Runbook 是摆设
  10. 团队没准备好

先把前 10 项做好,再考虑剩下的

八、常见问题 FAQ

Q1:异地多活和微服务是什么关系?

A:微服务是架构风格,异地多活是部署形态。两者正交:

  • 微服务可以单机部署(最简单)
  • 微服务可以同城双活部署(最常见)
  • 微服务可以异地多活部署(最高级)
  • 单体应用也可以做异地多活(少见但可行)

微服务让"单元化"更容易实施(服务粒度细,可以独立切分),但异地多活不强制要求微服务

Q2:异地多活和分布式事务是什么关系?

A强烈不建议在异地多活架构上用分布式事务

分布式事务(2PC / 3PC / TCC)依赖"事务协调者"在多个机房之间协调,跨机房延迟 30-100ms 会让事务延迟飙升到秒级。用户无法接受秒级的下单响应

异地多活的正确做法:通过单元化避免跨机房事务。如果实在无法避免(全局服务),用最终一致性(异步消息 + 幂等消费)替代强一致事务。

Q3:异地多活一定要用云厂商吗?

A:不一定。自建机房也可以做异地多活,但成本更高:

  • 自建机房要买地、买设备、招运维
  • 云厂商提供现成的 Region + 专线 + 中间件

多数公司选择公有云 + 多 Region 部署。少数大厂(阿里、字节)会自建机房 + 私有云。

Q4:异地多活最大的坑是什么?

A:根据多家公司公开复盘,最大的坑是"故障时不会切"

具体表现:

  • 切换脚本 3 年没演练过,目标机房 ID 是错的
  • 切换流程依赖某个离职员工的个人经验
  • 监控系统告警了但没人知道该做什么
  • 切过去后业务不通,又切回来,来回切几次

异地多活不是"建好就完事"没有持续演练的异地多活形同虚设

Q5:异地多活的成本能降下来吗?

A:能,但有上限。

降成本的方向:

  • 复用云厂商资源:用公有云 + 容器化,资源按需扩缩
  • 共享存储:用云数据库(RDS / PolarDB)替代自建 MySQL,运维成本降 50%
  • 自动化运维:用 K8s + Operator 自动化部署和切换
  • 共享中间件:用 Nacos / Sentinel / Seata 等开源组件替代自研

但底线是:异地多活的硬件成本不会低于同城的 3-5 倍。这是物理规律(要买双倍资源),不是软件能省的。

Q6:异地多活和 Service Mesh 是什么关系?

AService Mesh 是异地多活的"好搭档"

异地多活要求"禁止跨机房调用",Service Mesh 可以在 Sidecar 层面自动拦截所有跨机房调用:

  • 标记请求属于哪个机房
  • 拦截违规的跨机房调用
  • 自动重试 + 限流
  • 统一的灰度切流

没有 Service Mesh 也能做异地多活(靠代码规范 + 人工 Review),但有 Service Mesh 会轻松很多。Istio / Linkerd / 自研 Mesh 都是常见选择。

Q7:异地多活会影响性能吗?

A会,但可控

性能影响来源:

  • 入口打标 + 路由:每次请求多 1-3ms(路由层查询)
  • 同步复制:写入延迟可能增加(等同步完成)
  • 跨机房调用:理论上 0,违规时可能 30-100ms

通过单元化设计,可以把跨机房调用控制在 0.01% 以下。剩下的性能开销 ≈ 3-5ms,用户几乎感知不到。

九、推荐阅读

如果想深入异地多活的工程实践,以下资料值得一读:

书籍

  • 《数据密集型应用系统设计》(DDIA, Martin Kleppmann)—— 分布式系统圣经,第 5、6 章讲复制与分区
  • 《大型网站技术架构:核心原理与案例分析》(李智慧)—— 阿里技术专家作品,前几章讲架构演化

公开技术博客

  • 阿里中间件团队博客:异地多活、双 11 备战系列
  • 美团技术团队博客:OCTO 框架、单元化实践
  • 字节跳动技术博客:Service Mesh 与多活架构

开源组件

  • Canal:阿里开源 MySQL binlog 订阅
  • Otter:阿里开源数据库同步工具
  • RedisShake:Redis 数据同步
  • MongoShake:MongoDB 数据同步
  • Nacos:阿里开源服务发现 + 配置中心
  • Sentinel:阿里开源流量治理

💡 学习路径建议

  1. 入门:本文 + DDIA 第 5 章,理解复制模型
  2. 进阶:阿里中间件博客 + 美团 OCTO 系列,理解工业实践
  3. 实战:研究 Canal / Sentinel / Seata 源码,理解组件原理
  4. 深潜:动手做小规模双活 PoC,体验真实延迟与同步问题

理论 + 实践 + 源码,缺一不可。

参考文章

本系列共 16 篇,本文为第 1 篇 · 查看全部
使用 Hugo 构建
主题 StackJimmy 设计