
1. 为什么“两地三中心”成了高可用的代名词1.1 先回答那个最常被问的问题它凭什么比单机房高一档在做架构评审的这些年里我被问得最多的不是“怎么做高可用”而是“我们业务量还没到那个地步要不要上两地三中心”。说实话这个问题本身就说明很多人把两地三中心当成了一种“荣誉勋章”只要上了这个架构好像全年可用性就能自动拉到五个九。实际情况远没那么简单。两地三中心的字面意思很直白两个城市三个数据中心。通常做法是同城部署两个可用区平时一起承载业务流量互为备份再在另一个城市放一个灾备中心用来应对城市级故障。这套思路最早在金融行业被卷出来因为监管对核心系统的容灾能力有硬性要求后来互联网大厂也跟着用慢慢变成了高可用架构的标杆方案。我在阿里云上帮客户落地过不少这类架构从电商交易到会员体系都有。真正跑通之后你会发现它解决的不是“单台服务器挂了怎么办”而是一个更棘手的问题整个机房的电力断了、光缆被挖断了、甚至一个城市遇上极端灾害时你的系统还能不能继续对外服务。单机房的哪怕做得再冗余都逃不掉地理位置这个致命弱点两地三中心就是为了把这个弱点补上。1.2 搞懂 RPO 和 RTO再谈架构否则都是空话讨论两地三中心之前有两把尺子必须先摆出来RPO 和 RTO。RPORecovery Point Objective衡量的是故障发生后最多能丢多少数据单位是时间或数据量。RPO 等于 0 意味着一条数据都不能丢这是最苛刻的。RTORecovery Time Objective衡量的是从故障发生到系统恢复服务需要多长时间直接决定了你的容灾切换方案要设计得多快。这两把尺子不先定好后面所有技术选型都是空的。我给你举个例子你拍脑袋定了 RPO0那同城两个可用区之间就必须走强同步复制数据库写入要等两个副本都确认才算成功对延迟带宽要求极高。如果你能接受 RPO 在 5 分钟以内那异地异步复制就够了成本一下子降一个量级。我见过太多项目一上来就喊“我们要 RPO 等于 0”结果一期做下来预算超标、上线延期最后悄悄改成异步。所以我的习惯是接任何容灾项目的第一件事不是画架构图是拉着业务方坐在一起把 RPO 和 RTO 白纸黑字签下来。1.3 同城双活、异地灾备各自的边界在哪里两地三中心里同城和异地的定位完全不同不能混为一谈。同城两个可用区物理距离近内网延迟通常在 0.5 到 2 毫秒之间这个延迟足以支持做“双活”。所谓双活就是两个中心同时承接流量负载均衡把请求分发到两边任何一边挂了另一边继续扛用户在感知层面几乎无感。但注意同城双活通常是“应用双活”不是“数据库双活”。数据库在这套架构里一般还是主备模式主库写在 A 中心备库在 B 中心通过同步复制拉数据。真正的数据库双活也就是双主写入在分布式数据库里可以做到但复杂度会急剧上升脑裂风险也更大。异地灾备中心的角色就简单得多它不承担生产流量只负责把数据异步复制过去。平时那套环境甚至可以只跑最基础的校验任务故障发生时才切换过去接管。但异地中心最大的价值是保底同城两个中心一起挂掉这个概率虽然低却不是零只有异地能兜住。2. 落地之前先把这三件事想明白不然就是烧钱2.1 业务分级不是所有业务都配得上两地三中心两地三中心最大的误解就是“整个公司所有系统全部上”。实际上哪怕阿里云内部的业务也不是这么干的成本撑不住运维复杂度也会把你淹没。我刚做这类项目时也栽过跟头客户要求“全量上两地三中心”结果连内部审批系统都要做异地容灾浪费了大量机器和带宽。后来我们把业务分成三类核心业务、重要业务、普通业务。核心业务比如订单、支付、会员资产必须上两地三中心并且要定期演练重要业务做到同城双活加异地备份不要求快速切换普通业务保持单可用区部署靠多副本和备份保住基本可用性就行。这个分级听起来是常识但在真实项目中特别容易被忽略。业务方只要听说“容灾”就觉得所有系统都应该一视同仁这时候架构师得顶住压力告诉他们容灾也是有性价比的。2.2 你以为是买机器其实是买带宽和数据同步很多人算两地三中心的成本只算了三份机器的钱这是最大的误区。我复盘过一个真实项目三地机房各放了一批 ECS 和 RDS机器成本确实可控但到了月底账单一看跨可用区的流量费、DTS 数据同步的实例费用、GTM 流量调度费用这些加起来比 ECS 还贵。尤其是异地数据同步主备两个 RDS 之间持续拉取 binlog带宽费用是持续上涨的。在阿里云上同可用区之间流量免费但跨地域的流量是计费的C 中心在异地每天同步几十 GB 的变更数据下来光流量成本就够买两台高配服务器。所以预算阶段一定要把“同步带宽”和“同步链路实例”这两项单独列出来并且按业务增长预留 1.5 到 2 倍余量。2.3 容灾指标决定了架构架构决定了产品选型这里我强烈建议你把 RPO 和 RTO 的结论落到一张表格里再去选型而不是先选型再定指标。指标同城主备同城双活 异地异步两地三中心强同步RPO通常为 0半同步复制异地部分 5 秒到分钟级0 到秒级RTO1 到 5 分钟分钟到十几分钟分钟级成本低中高极高适合业务非核心但重要的系统互联网核心业务金融级强一致场景多数互联网业务选中间那一档就够了同城双活保证日常可用性异地异步复制兜底城市级故障。真正需要三地强同步的业务并不多而且强同步对带宽和延迟极其敏感异地相隔几百公里时RTO 很难压到秒级。3. 架构全景从网络到中间件每一层都要能扛故障3.1 同城双活核心是把应用做成“无状态”同城双活听起来很高级实现起来有一个硬前提应用必须无状态。这是很多人忽略的第一步。什么叫有状态Session 存在本地内存里就是有状态文件只写本地磁盘就是有状态。这种应用部署在两个机房是没法真正双活的因为用户第一次请求打到 A 中心第二次被负载均衡发到 B 中心Session 就找不到了。所以做两地三中心的第一步是把 Session、临时文件、本地缓存全部迁移到分布式组件上Session 放 Redis文件放 OSS状态放数据库。做这一步的工程量取决于你代码的规范程度如果历史包袱大这一步可能比后面所有工作加起来都耗时。我经手过一个老项目为了把临时上传的文件从本地磁盘改成 OSS光改造接口就用了两周。但这一步不做后面的双活全是空中楼阁。无状态改造完成之后部署形态就清晰了在 A 可用区和 B 可用区各部署一批 ECS前端挂阿里云 SLB负载均衡算法选加权轮询双中心各承担一半流量。SLB 本身支持多可用区部署健康检查会定期探测后端的 ECS某一边整体异常时流量自动全部导向另一边。3.2 异地灾备中心热备还是冷备我建议至少让一半读流量进去异地灾备中心最常见的坑是“备用环境真的成了备用”常年不接流量只在演练和故障时启动。结果真的等到要切换才发现依赖配置没同步、大数据任务没跑、启动脚本早就失效。我的做法是异地中心至少承担部分读流量或者跑一些不敏感的业务运算。比如把商品详情页的读流量在异地中心也放一份主中心挂了异地中心至少能马上接管读服务就算什么都不承担也应该保证这套环境每个季度能完整启动一次跑一遍核心接口的冒烟测试。好处不止是验证可用性还能让运维团队对异地环境保持着操作熟练度真正出事时不至于手忙脚乱。3.3 中间件层Redis、MQ 也要有“容灾位置”很多人把精力都花在数据库上却忽略了中间件。实际上 Redis、消息队列这类组件一旦挂掉影响面一点不比数据库小。Redis 建议部署成多可用区集群。阿里云的 Redis 产品可以跨可用区部署副本节点主节点在 A备节点在 B发生机房级故障时自动进行主备切换。但缓存这东西本身可以允许短暂的不一致所以不需要做异地实时同步异地中心如果条件允许可以放一个只读副本或者干脆在故障切换时冷启动重建缓存毕竟缓存数据可以从数据库回源。消息队列则是另外一个思路。RocketMQ 或 Kafka 的副本机制本来就能保证同城多可用区的高可用但异地同步消息是很谨慎的事。我建议异地灾备中心不要直接消费生产环境的全部消息只同步关键业务的消息或者干脆异步拉取离线数据。因为消息顺序和幂等性在跨地域场景下非常容易出问题同步全量消息会把故障面人为放大。3.4 数据中心组织起来之后长这样一个典型的阿里云两地三中心架构可以这样描述两个同城可用区组成生产集群通过专有网络 VPC 打通采用同城双活部署异地可用区作为灾备中心通过云企业网或者高速通道与本地 VPC 互联数据库通过 DTS 持续同步。数据流向是业务流量进 SLB分发到 A、B 两区的应用集群应用写数据库主库主库同步到同城备库同时异步复制到异地灾备库。这套架构能扛住三种故障ECS 级别的故障靠负载均衡摘除节点可用区级别的故障靠同城双活接管城市级别的故障靠异地灾备中心兜底。层级分明每一种故障都有对应的“接盘侠”这就是两地三中心的价值所在。4. 数据库层整个容灾系统的真正胜负手4.1 为什么数据库最难它有状态还有顺序如果只让我选一个最复杂的环节那必然是数据库。应用层无状态之后随便你怎么水平扩展都行但数据库不一样它保存着所有最终数据而且数据写入是有先后顺序的顺序错乱就会带来数据不一致。两地三中心里数据库要做两件事同城提供高可用异地提供灾备。这两件事对数据库的要求是矛盾的。同城要求复制延迟低、数据尽量不丢异地要求能容忍带宽和网络抖动。所以同城和异地必须分开设计不能用一条同步链路包打天下。4.2 同城双活数据库主备是你的最好朋友我在前面提到过同城双活不等于数据库双主。我见过不少团队试图在同一个 MySQL 集群里搞双主结果就是为了解决主键冲突花了大量精力最后每次故障还是得人工介入。生产环境里我推荐的做法是同城主备主库在 A 可用区备库在 B 可用区。A 区的应用直连主库B 区的应用也通过只读地址访问备库主库和备库之间开启半同步复制。所谓半同步就是主库执行完事务后必须等备库收到日志才向客户端返回成功这样能最大限度降低 RPO。这里有个容易被误解的细节半同步复制在备库宕机时会自动退化为异步复制否则主库就写不进去了。所以半同步只能做到“大部分时间不丢数据”不是绝对的 RPO0。要做到完全 RPO0就得引入 Paxos 或 Raft 协议的分布式数据库比如阿里云的 PolarDB-X它的多副本一致性是真正意义上的强一致。但代价是性能损耗和架构复杂度得按业务需要取舍。4.3 异地灾备DTS 链路要能自愈延迟监控要盯紧异地的数据同步我基本都用阿里云 DTS 来做。DTS 会读取主库的增量日志解析成对目标库可执行的语句或者网络传输协议持续复制到异地灾备库。实际操作为了防止主中心和灾备中心数据库结构不一致我通常先在 DTS 里做一次全量数据迁移再启动增量同步。全量迁移期间业务可以不间断写入DTS 会记录迁移开始时的时间点然后持续追平增量最终两边数据一致。同步链路最怕的是延迟。异地网络抖动是常态DTS 一旦延迟超过几分钟你需要第一时间能看到。我习惯给每个 DTS 同步实例配置延迟告警阈值设在 30 秒超过就打电话。因为延迟不是孤立的它意味着异地灾备库已经落后很多了这时候主中心要是挂了切过去也救不回最后几分钟的数据。4.4 没做过数据校验的灾备等于没有灾备我强烈建议灾备环境不能只靠 DTS 同步完就不管了。你要定期做数据校验。有一次我们在做演练前的数据比对发现异地灾备库某个用户表的主键自增序列比主库大了一截。排查下来原来是前段时间做数据修复时直接往灾备库插过测试数据。线上的人根本不会去灾备库插数据可一旦真发生切换这个问题会在一瞬间爆发出来。所以每季度定期跑数据校验任务比对主中心和灾备中心的核心表行数、关键字段聚合值是必须的。DTS 提供的数据校验功能可以用也可以自己写简单的 count 比对任务。重点不是工具多强大而是你有没有真的在持续做这件事。5. 流量切换别让用户感觉到“你切了”5.1 DNS 是切换的第一道闸门但裸 DNS 不够用要做故障切换首先要解决“流量怎么找到新节点”的问题。很多人第一反应是改 DNS 解析把域名从 A 中心切到 B 中心。但原生 DNS 最大的问题是解析记录有 TTL 缓存你可能改了记录但全球各地的 Local DNS 还是拿着旧记录继续解释宽松一点要等十几分钟才能全部生效。所以我基本都建议用阿里云 GTM也就是全局流量管理。GTM 本质是智能 DNS 加上健康检查它给你一个 CNAME 域名然后内部配置多个接入点。每个接入点绑定不同可用区的负载均衡地址GTM 会实时探测各接入点的健康状态发现某个接入点异常自动把解析结果切换到健康的接入点并且 TTL 可以设得很短。在实际切换时GTM 能帮我们把流量切换从“改 DNS 记录”这种手工动作变成“点击某个接入点下线”这样的标准操作快而且不容易出错。5.2 切换动作要分三类手动、半自动、全自动我强烈反对一上来就做全自动切换。全自动意味着故障检测系统需要极高的准确率一旦误判比如某地区网络抖动导致健康检查误报流量就会在几个中心之间弹来弹去造成比故障本身更严重的二次伤害。我习惯把切换能力分成三种模式手动、半自动、全自动。平时运转模式是半自动系统检测到主中心异常触发告警值班架构师确认故障级别然后在 GTM 控制台点一下切换按钮。这样做既能快速响应又留了人工判断的空间。全自动模式只在特定场景启用比如完全不可控的机房全故障并且有严格的保护机制比如连续多次健康检查失败、另一中心确认存活才会真正触发。如果你刚上手两地三中心建议先把手动和半自动跑熟全自动等演练足够多以后再考虑。5.3 演练一次切换你就知道哪里会卡壳我参与过的第一次两地三中心切换演练原本计划 30 分钟内完成结果整整用了两个半小时。卡壳的地方不是技术而是流程。切换前需要确认主库是否已经停止写入、DTS 同步延迟是否归零、异地灾备库的数据是否追平、应用配置是否指向了新中心。这些检查项分散在不同团队的文档里没有人把它们整理成一份切换清单导致现场临时翻文档。所以从那以后我强制要求团队把切换过程做成标准操作手册每一步都写清负责人和预期耗时。后来再演练基本都能控制在 20 分钟以内。这套清单平时没用故障时就是救命稻草。6. 落地过程中最容易踩的坑6.1 脑裂两边都认为自己是“主”这是最恐怖的故障分布式系统的老话题在两地三中心场景下变得更现实。同城双中心的网络如果是通的一切好说可要是 A、B 之间的互联链路出了故障两边应用都还活着数据库主库和备库互相看不到对方。如果备库检测不到主库心跳就尝试自动切换双主就出现了两边同时接受写入数据开始分叉。要避免脑裂一是要有仲裁机制也就是通过第三方节点来判断“谁才是活着的那个”二是数据库层保持主备模式不给备库自动提升的权限。前者依赖架构设计后者依赖严格的运维纪律。在阿里云上我们一般让数据库的 HA 组件自己管理仲裁业务层不要自己开发自动切换逻辑自己写逻辑很容易写出边界条件没考虑清楚的神坑。6.2 跨地域一条同步调用性能直接掉一个量级同城双活的延迟很低你写代码时可以无感知。异地就不一样了两个城市之间的距离哪怕只有两三百公里专线延迟也在 30 到 80 毫秒之间。如果业务代码里有跨地域的同步调用比如订单服务在 A 区需要同步调用异地 C 区的某个服务每次请求多出 60 毫秒甚至更久高峰期连接池会瞬间被打满。所以我在设计异地灾备中心时会强制要求所有跨地域调用改成异步消息或者干脆禁止异地调用。真正的异步化改造是个大工程但比故障时盲目切换导致雪崩要靠谱得多。6.3 “备用”环境从没被用过一用就崩备用环境有个通病叫灰姑娘现象平时没人穿那双水晶鞋舞会当天才发现不合脚。常见场景是异地灾备中心的 ECS 安全组规则和生产环境不一致数据库账号密码过期配置文件里的中间件地址还指向同城中心。这些都是在演练或真实故障时才会暴露出来的。解决办法没有捷径就是按我前面说的让异地环境承担一部分真实读流量或者周期性做切换演练让这套环境保持“活着”的状态。6.4 切换过去了回不来了很多团队只演练“切换出去”从不演练“切回来”。真实故障恢复后你不仅要把流量切回主中心还要保证数据从灾备中心反向追平到主中心这个过程比正向切换更复杂。主中心修复后你需要让灾备中心停写、导出数据、追平主中心然后重建复制关系。这个过程中但凡某个步骤出错就可能造成数据回错。我建议回切动作必须在业务低峰期操作并且每次回切都当成一次正式演练来对待。7. 从“能容灾”到“敢切换”练出来的信心7.1 容灾演练的目标是验证“你敢不敢按下那个按钮”很多团队的容灾方案写在文档里架构图画得漂漂亮亮但真到了要按下“切换”按钮的那一刻谁都不敢动。这不是团队胆小而是没有足够的演练数据支撑信心。所以我推荐用混沌工程的思路来做演练定期在生产环境或预发环境注入故障比如直接停掉一个可用区的所有交换机、杀掉数据库主库节点、模拟 DNS 故障。每次注入故障后记录系统恢复的时间和路径持续评估 RTO 有没有达标。演练频率上同城级别的故障我建议一个月一次异地切换至少一季度一次。频率太低流程会生疏频率太高业务团队会疲劳投入产出比反而下降。7.2 先从读流量灰度切换开始再尝试全量切换如果你对全部流量切换没有信心可以设计一个灰阶切换策略。第一次演练只切换 10% 的读流量到异地中心监控错误率和延迟指标等稳定了再逐步放量。这个思路特别适合第一次从“单中心 备份”迁移到“两地三中心”的团队。灰度切换可以帮助团队在真实流量中发现那些静态检查根本发现不了的问题比如依赖了同城专有的内网 DNS、调用了本地的存储路径等等。等灰度流程跑顺了再尝试写流量切换最后才做完整流程演练。7.3 复盘的时候只看三个数字容灾演练结束后的复盘会我一般只盯三个数字实际 RPO、实际 RTO、切换成功率。实际 RPO 看的是演练过程中到底丢了多少数据如果 DTS 延迟降到 0但主库 binlog 没有清理干净可能导致灾备库读不到日志RPO 就会被迫放大。实际 RTO 看的是从故障注入到业务恢复的时间需要精确到分钟并和既定目标对比。切换成功率则是把切换清单每一项过一遍看有没有哪一步需要人工补救。这三个数字是红色的说明方案还有缺口是绿色的才意味着你有资格在老板面前说“两地三中心已经跑通”。7.4 落地路线图按这个顺序做不容易乱最后分享一个我常用的落地顺序按这个走能让团队的压力小很多先做业务分级和 RPO/RTO 定标所有干系人签字确认完成应用无状态化改造这一步不完成后面全是空谈在同城部署第二可用区先做数据库主备同步再做 SLB 多可用区接入验证同城双活做一次小流量故障演练建立异地灾备中心用 DTS 打通数据同步链路配置 GTM 和健康检查实现域名层面的故障转移接手全流程演练按月度和季度节奏持续迭代。这个顺序的最大好处是每一步都有独立的验收点前一步没验证完绝不进入下一步。整个项目过程中我个人的体会是真正难的从来不是某个具体的云产品怎么配而是你有没有把团队的操作流程、评审标准和演练节奏真正建立起来。两地三中心是从架构到组织的一次整体升级它考验的是团队的容灾思维而不只是机器和带宽。