在软件开发领域,异地多活是分布式系统架构设计中的一座高峰。很多人听过它,却很少能真正说清背后的原理:它到底解决什么问题?又是如何解决的?
我有幸深度参与过一个中等规模互联网公司异地多活系统的设计与实施过程,今天就把背后的实现原理拆开讲一讲。内容确实有点干,建议你耐心读完。

01 系统可用性
要理解异地多活,得先从架构设计的基本原则说起。
如今开发一个软件系统,要求越来越高。如果你了解架构设计,就会知道一个良好的软件架构通常要遵循 3 个原则:
- 高性能
- 高可用
- 易扩展
其中,高性能意味着系统拥有更大的流量处理能力和更低的响应延迟。例如 1 秒能处理 10W 并发请求,接口响应时间控制在 5ms 以内。
易扩展表示系统在迭代新功能时,能以最小代价扩展;遇到流量压力时,可以不改动代码就完成扩容。
而“高可用”这个概念比较抽象,通常用 2 个指标来衡量:
- 平均故障间隔 MTBF(Mean Time Between Failure):两次故障之间的间隔时间,也就是系统“正常运行”的平均时间。这个时间越长,说明系统稳定性越高。
- 故障恢复时间 MTTR(Mean Time To Repair):系统发生故障后“恢复”的时间。这个值越小,故障对用户的影响就越小。
可用性与两者的关系如下:
可用性(Availability)= MTBF / (MTBF + MTTR) × 100%
得到的结果是一个“比例”,通常我们会用“N 个 9”来描述系统的可用性。

从这张表可以看出,要想达到 4 个 9 以上的可用性,平均每天的故障时间必须控制在 10 秒以内。也就是说,只有故障时间越短,系统可用性才会越高。每提升 1 个 9,都会对系统提出更高要求。
系统发生故障不可避免,规模越大的系统,出问题的概率也就越大。故障通常体现在 3 个方面:
- 硬件故障:CPU、内存、磁盘、网卡、交换机、路由器
- 软件问题:代码 Bug、版本迭代
- 不可抗力:地震、水灾、火灾、战争
这些风险随时可能发生。因此,面对故障时,系统能否以“最快”的速度恢复,就成了可用性的关键。
可如何实现快速恢复呢?
这篇文章要讲的 异地多活 架构,就是为了解决这个问题而提出的高效方案。
下面我会从一个最简单的系统出发,带你一步步演化出支持异地多活的系统架构。在这个过程中,你会看到系统会遇到哪些可用性问题,以及为什么架构需要这样演进。
02 单机架构
从最简单的场景讲起。
假设业务处于起步阶段,体量非常小,那架构大致是这样的:

这个模型非常简单:客户端请求进来,业务应用读写数据库,返回结果,很好理解。
但需要注意的是,这里的数据库是“单机”部署的,所以有一个致命缺点:一旦遇到磁盘损坏、操作系统异常、误删数据等意外,所有数据就会全部“丢失”,损失巨大。
如何避免?很容易想到一个方案:备份。

你可以对数据做备份,把数据库文件定期 cp 到另一台机器上。这样即使原机器数据丢失,仍然可以通过备份把数据恢复回来,保证数据安全。
这个方案虽然简单,但存在 2 个问题:
- 恢复需要时间:业务需要先停机再恢复数据,停机时间取决于恢复速度,恢复期间服务“不可用”。
- 数据不完整:因为是定期备份,数据肯定不是“最新”的,完整性取决于备份周期。
很明显,数据库越大,故障恢复时间越久。按照前面提到的“高可用”标准,这个方案可能连 1 个 9 都达不到,远无法满足我们对可用性的要求。
那有没有更好的方案,既能快速恢复业务,又能尽可能保证数据完整性呢?
这时可以采用:主从副本。
03 主从副本
在另一台机器上再部署一个数据库实例,让新实例成为原实例的“副本”,并让两者保持“实时同步”。架构如下:

通常把原实例叫作主库(master),新实例叫作从库(slave)。这个方案的优点在于:
- 数据完整性高:主从副本实时同步,数据“差异”很小。
- 抗故障能力提升:主库异常时,从库可随时“切换”为主库,继续提供服务。
- 读性能提升:业务应用可以直接读从库,分担主库的读压力。
这个方案不错,不仅大幅提高了数据库的可用性,还提升了系统的读性能。
同样的思路,业务应用也可以在其它机器上部署一份,避免单点。因为业务应用通常是“无状态”的,不像数据库那样存储数据,直接部署即可,实现上非常简单。

因为业务应用部署了多个节点,所以还需要引入一个接入层来做请求的 负载均衡(一般会使用 nginx 或 LVS)。这样当一台机器宕机后,另一台机器可以“接管”所有流量,持续提供服务。

从这个方案可以看出,提升可用性的核心思路就是:冗余。
没错,单实例怕故障,就部署多个实例;单机器怕宕机,就部署多台机器。
到这里,架构已经基本演进为主流方案,之后开发的新业务应用都可以按照这个模式部署。

但这种方案还有风险吗?
04 风险不可控
现在把视角拉近,聚焦到具体的“部署细节”上。
按照前面的分析,为了避免单点故障,应用已经部署在多台机器上。但这些机器具体分布在什么位置,我们并没有深究。
一个机房有很多服务器,通常会分布在一个个“机柜”上。如果使用的机器恰好都在同一个机柜,仍然存在风险。比如,连接这个机柜的交换机或路由器发生故障,应用依旧有“不可用”的可能。
虽然交换机和路由器通常做了线路冗余,但并不能保证绝对不出问题。
部署在一个机柜有风险,那把机器打散到不同机柜,是不是就没问题了?
这样确实能大大降低出问题的概率。但我们仍不能掉以轻心,因为无论怎么分散,它们始终处于同一个环境中:机房。
那么,机房会不会发生故障呢?
一般来说,建设一个机房的门槛很高,地理位置、温湿度控制、备用电源等各方面都要做好防护。但即便如此,每隔一段时间我们仍会看到这样的新闻:
- 2015 年 5 月 27 日,杭州市某地光纤被挖断,近 3 亿用户长达 5 小时无法访问支付宝
- 2021 年 7 月 13 日,B 站部分服务器机房发生故障,整站持续 3 个小时无法访问
- 2021 年 10 月 9 日,富途证券服务器机房发生电力闪断故障,用户 2 个小时无法登录和交易
- ……
可见,即使机房级防护做得足够好,只要有“概率”出问题,现实就有发生的可能。概率虽小,但一旦发生,影响范围极大。
看到这里你可能会想:机房出问题的概率也太小了,工作这么多年也没碰上过,有必要考虑得这么复杂吗?
但有没有想过:不同体量的系统,各自关注的重点是什么?
体量很小的系统,重点关注“用户规模”和增长,这个阶段获取用户是核心。等用户体量上来后,会重点关注“性能”,优化接口响应时间、页面打开速度等,更多关注用户体验。
当体量再大到一定规模后,“可用性”就变得尤为重要。像微信、支付宝这样的全民级应用,机房一旦发生故障,影响范围会非常巨大。
所以,再小概率的风险,在提升系统可用性时也不能忽视。
分析完风险,再说回架构:到底该怎么应对机房级别的故障?
答案还是:冗余。
05 同城灾备
要抵御“机房”级别的风险,方案就不能再局限在一个机房内。
现在需要做机房级别的冗余,也就是说,再搭建一个机房来部署服务。
简单起见,你可以在“同一个城市”再搭建一个机房。原机房叫作 A 机房,新机房叫 B 机房,两个机房之间用一条“专线”连通。

有了新机房,怎么把它用起来?这里仍然要优先考虑“数据”风险。
为了避免 A 机房故障导致数据丢失,需要把数据在 B 机房也存一份。最简单的方案还是前面提到的:备份。
A 机房的数据定期在 B 机房做备份,拷贝数据文件。这样即使整个 A 机房严重损坏,B 机房的数据也不会丢,可以通过备份恢复回来,重启服务。

这种方案我们称为“冷备”。为什么叫冷备?因为 B 机房只做备份,不提供实时服务,它是“冷”的,只有在 A 机房故障时才会启用。
但备份的问题依然和之前一样:数据不完整、恢复期间业务不可用,系统可用性依然无法保证。
所以,还是需要用“主从副本”的方式,在 B 机房部署 A 机房的数据副本。架构变成这样:

这样,即使整个 A 机房挂掉,B 机房也有比较“完整”的数据。
数据保住了,但此时还要考虑另一个问题:如果 A 机房真的挂掉,要想保证服务不中断,还需要在 B 机房“紧急”做这些事:
- B 机房所有从库提升为主库
- 在 B 机房部署应用,启动服务
- 部署接入层,配置转发规则
- DNS 指向 B 机房,接入流量,业务恢复
看到了吗?A 机房故障后,B 机房需要做这么多工作,业务才能完全恢复过来。
整个过程需要人工介入,并且要花费大量时间。恢复之前,整个服务依然不可用。这个方案还不够理想,如果能做到故障后立即“切换”,那就好多了。
因此,要缩短业务恢复时间,就必须把这些工作在 B 机房“提前”做好。也就是说,B 机房要提前部署好接入层、业务应用,随时等待切换。架构变成这样:

这样的话,A 机房整个挂掉,我们只需要做 2 件事:
- B 机房所有从库提升为主库
- DNS 指向 B 机房,接入流量,业务恢复
恢复速度因此快了很多。
到这里你会发现,B 机房从最开始的“空空如也”,已经演变到几乎“镜像”了 A 机房的所有组件:从最上层的接入层,到中间的业务应用,再到最下层的存储。
两个机房唯一的区别是:A 机房的存储都是主库,B 机房都是从库。
这种方案,我们把它叫作“热备”。
热备中的 B 机房处于“待命”状态,A 故障后可以随时“接管”流量,继续提供服务。热备相比冷备最大的优点就是:随时可切换。
无论是冷备还是热备,因为都处于“备用”状态,所以这两个方案统称为:同城灾备。
同城灾备的最大优势在于,我们再也不用担心“机房”级别的故障了。一个机房发生风险,只需把流量切到另一个机房即可,可用性再次提升。
06 同城双活
继续看这个架构。
虽然现在已经有了应对机房故障的方案,但仍有一个不能忽视的问题:A 机房挂掉后,全部流量切到 B 机房,B 机房真的能如我们所愿正常提供服务吗?
这个问题值得认真思考。
这就好比有两支军队 A 和 B。A 军队久经沙场,作战经验丰富;而 B 军队只是后备军,除了具备军人基本素养之外,并没有实战经验,战斗经验基本为 0。
如果 A 军队丧失战斗能力,需要 B 军队立即顶上,作为指挥官的你是不是也会担心:B 军队真的能担此重任吗?
架构同理。此时的 B 机房虽然是随时“待命”状态,但 A 机房真的发生故障时,要把全部流量切到 B 机房,其实并不敢百分百保证它可以“如期”工作。
想想看,即便在单个机房内部部署服务,也总会遇到各种问题:发布版本不一致、系统资源不足、操作系统参数不一样等等。现在多部署一个机房,这些问题只会增多,不会减少。
另外从“成本”角度看,新部署一个机房需要购买服务器、内存、硬盘、带宽资源,成本高昂。只让它当后备军,未免太“大材小用”了。
因此,需要让 B 机房也接入流量,实时提供服务。这样做的好处是:
- 实时训练这支后备军,让它达到与 A 机房相同的作战水平,随时可切换。
- B 机房接入流量后,可以分担 A 机房的流量压力。
这才是充分发挥 B 机房资源优势的最好方案。
那怎么让 B 机房也接入流量呢?很简单,把 B 机房的接入层 IP 地址加入 DNS 即可。这样,B 机房从上层就能有流量进来。

但这里有一个问题:别忘了,B 机房的存储现在都是 A 机房的“从库”,从库默认是“不可写”的。B 机房的写请求打到本机房存储上,肯定会报错,这不符合预期。怎么办?
这时,就需要在“业务应用”层做改造。
业务应用在操作数据库时,需要区分“读写分离”(一般用中间件实现)。也就是说,两个机房的“读”流量可以读任意机房的存储,但“写”流量只允许写 A 机房,因为主库在 A 机房。

这涉及项目中用到的所有存储,例如 MySQL、Redis、MongoDB 等。操作这些数据库都需要区分读写请求,因此会有一定的业务“改造”成本。
因为 A 机房的存储都是主库,所以把 A 机房叫作“主机房”,B 机房叫“从机房”。
两个机房部署在“同城”,物理距离较近,而且通过“专线”网络连接。虽然跨机房访问的延迟比单个机房内大一些,但整体延迟仍可接受。
业务改造完成后,B 机房可以逐步接入流量,从 10%、30%、50% 逐渐覆盖到 100%。期间可以持续观察 B 机房业务是否存在问题,有问题及时修复,逐渐让 B 机房的工作能力达到与 A 机房相同水平。
现在,因为 B 机房实时接入了流量,如果 A 机房挂了,就可以“大胆”地把 A 的流量全部切到 B 机房,完成快速切换。
到这里可以看到,部署的 B 机房虽然物理上与 A 有一定距离,但从系统“逻辑”上,我们是把这两个机房当作一个“整体”来规划的,也就是把 2 个机房当作 1 个机房来用。
这种架构方案比同城灾备更进一步:B 机房实时接入流量,还能应对随时切换。我们把这种方案叫作“同城双活”。
因为两个机房都能处理业务请求,系统内部维护、改造、升级就获得了更多可实施空间,系统弹性也变大了。
那这种架构又有什么问题呢?
07 两地三中心
还是回到风险上。
虽然我们把 2 个机房当做一个整体来规划,但这 2 个机房在物理上仍处于“同一城市”。如果整个城市发生自然灾害,例如地震、水灾,那 2 个机房依然存在“全局覆没”的风险。
真是防不胜防。怎么办?没办法,继续冗余。
但这次冗余机房就不能部署在同一城市了,需要把它放到距离更远的地方,部署在“异地”。
通常建议两个机房的距离在 1000 公里以上,这样才能应对城市级别的灾难。
假设之前的 A、B 机房在北京,那这次新部署的 C 机房可以放在上海。
按照前面的思路,把 C 机房用起来,最简单粗暴的方案还是做“冷备”:定时把 A、B 机房的数据在 C 机房做备份,防止数据丢失。

这种方案,就是我们常听到的“两地三中心”。
两地是指 2 个城市,三中心是指有 3 个机房。其中 2 个机房在同一个城市,并且同时提供服务;第 3 个机房部署在异地,只做数据灾备。
这种架构通常用在银行、金融、政企相关项目中。它的问题还是前面所说的:启用灾备机房需要时间,而且启用后的服务能否如期工作,也存在不确定性。
所以,要想真正抵御城市级别的故障,越来越多互联网公司开始实施“异地双活”。
08 伪异地双活
这里继续分析 2 个机房的架构。我们不再把 A、B 机房放在同一城市,而是分开部署,例如 A 机房在北京,B 机房在上海。
前面讲了同城双活,那异地双活是不是直接“照搬”同城双活的模式部署就可以了呢?
事情没那么简单。
如果仍按同城双活的架构部署,异地双活的架构会是这样:

注意看,两个机房通过“跨城专线”连通。
此时两个机房都接入流量,上海机房的请求可能要读写北京机房的存储,这里存在一个很大的问题:网络延迟。
因为两个机房距离较远,受物理距离限制,两地之间的网络延迟变成了“不可忽视”的因素。
北京到上海的距离大约 1300 公里。即便架设一条高速“网络专线”,光纤以光速传输,一个来回也需要近 10ms 的延迟。
况且,网络线路沿途还要经过各种路由器、交换机等网络设备,实际延迟可能达到 30ms~100ms。如果网络发生抖动,延迟甚至可能达到 1 秒。
不仅延迟大,远距离网络专线的质量也远达不到机房内网络质量。专线网络经常会发生延迟、丢包甚至中断。总之,不能过度信任和依赖“跨城专线”。
有人可能会问:这点延迟对业务影响很大吗?
影响非常大。
试想,一个客户端请求打到上海机房,上海机房要去读写北京机房的存储。一次跨机房访问延迟就达到 30ms,大致是机房内网网络(0.5ms)访问速度的 60 倍。一次请求慢 60 倍,来回往返就要慢 100 倍以上。
而在 App 中打开一个页面,可能会访问后端几十个 API。如果每次都跨机房访问,整个页面的响应延迟就有可能达到“秒级”,这种性能表现完全无法接受。
看到了吗?虽然只是简单地把机房部署在“异地”,但“同城双活”的架构模型在这里已经不适用了。如果仍按这种方式部署,那就是“伪异地双活”。
那如何做到真正的异地双活呢?
09 真正的异地双活
既然“跨机房”调用延迟是不容忽视的因素,那就只能尽量避免跨机房“调用”,从而规避延迟问题。
也就是说,上海机房的应用,不能再“跨机房”去读写北京机房的存储,只允许读写上海本地的存储,实现“就近访问”,这样才能避免延迟。
但前面提过的问题又来了:上海机房存储都是从库,不允许写入,除非只允许上海机房接入“读流量”,不接收“写流量”,否则无法满足不跨机房的要求。
显然,只让上海机房接收读流量的方案不现实,因为很少有项目只有读流量而没有写流量。
这时,就必须在“存储层”做改造。
要让上海机房读写本机房的存储,上海机房的存储就不能再是北京机房的从库,而是也要变成“主库”。
没错,两个机房的存储必须都是“主库”,并且数据要“互相同步”。也就是说,客户端无论写哪一个机房,都能把这条数据同步到另一个机房。
因为只有两个机房都拥有“全量数据”,才能支持任意切换并持续提供服务。
怎么实现这种“双主”架构?它们之间如何互相同步数据?
如果你了解 MySQL,就知道 MySQL 本身提供双主架构,支持双向复制数据,但实际用得并不多。而且 Redis、MongoDB 等数据库并没有提供这个功能,所以必须开发对应的“数据同步中间件”来实现双向同步。
此外,除了数据库这类有状态软件之外,项目通常还会用到消息队列,例如 RabbitMQ、Kafka。这些也是有状态服务,同样需要开发双向同步中间件,支持任意机房写入数据并同步到另一个机房。
可以看到,复杂度一下子就上来了。单单针对每个数据库、队列开发同步中间件,就需要投入很大精力。
业界也开源了不少数据同步中间件,例如阿里的 Canal、RedisShake、MongoShake,可以分别在两个机房同步 MySQL、Redis、MongoDB 数据。
很多有能力的公司也会采用自研同步中间件的方式,例如饿了么、携程、美团都开发了自己的同步中间件。
现在,整个架构就变成了这样:

注意看,两个机房的存储层是互相同步数据的。有了数据同步中间件,可以达到这样的效果:
- 北京机房写入 X = 1
- 上海机房写入 Y = 2
- 数据通过中间件双向同步
- 北京、上海机房都有 X = 1、Y = 2 的数据
这里用中间件双向同步数据,就不用再担心专线问题。专线出问题时,中间件可以自动重试,直到成功,最终达到数据一致。
但仍然会遇到一个问题:两个机房都可以写,如果操作的不是同一条数据还好;如果修改的是同一条数据,发生冲突怎么办?
- 用户短时间内发出 2 个修改请求,修改同一条数据
- 一个请求落在北京机房,修改 X = 1(尚未同步到上海机房)
- 另一个请求落在上海机房,修改 X = 2(尚未同步到北京机房)
- 两个机房以哪个为准?
也就是说,在极短时间内,同一个用户修改同一条数据,两个机房无法确认谁先谁后,数据发生“冲突”。
这是一个很严重的问题。系统故障不可怕,真正可怕的是数据发生“错误”,因为修正数据的成本太高了。这个问题必须避免。解决它有 2 个方案。
第一个方案:数据同步中间件要具备自动“合并”数据、解决“冲突”的能力。
这个方案实现起来比较复杂。要想合并数据,就必须区分“先后”顺序。最容易想到的方案是以“时间”为标尺,以“后到达”的请求为准。
但这种方案需要两个机房的“时钟”严格保持一致,否则很容易出问题。例如:
- 第 1 个请求落到北京机房,北京机房时钟是 10:01,修改 X = 1
- 第 2 个请求落到上海机房,上海机房时钟是 10:00,修改 X = 2
因为北京机房时间“更晚”,最终结果就会是 X = 1。但这里其实应该以第 2 个请求为准,即 X = 2 才对。
可见,完全依赖时钟的冲突解决方案,并不严谨。
所以,通常会采用第二种方案:从“源头”就避免数据冲突的发生。
10 如何实施异地双活
既然自动合并数据的方案实现成本高,那就要考虑能否从源头就“避免”数据冲突。
这个思路非常棒。
从源头避免数据冲突的思路是:在最上层接入流量时,就不要让冲突发生。
具体来说,就是在最上层把用户“区分”开:部分用户请求固定打到北京机房,其它用户请求固定打到上海机房。进入某个机房的用户请求,之后所有业务操作都在这个机房内完成,从根源上避免“跨机房”。
所以这时,需要在接入层之上再部署一个“路由层”(通常部署在云服务器上),自己配置路由规则,把用户“分流”到不同机房。

这个路由规则具体怎么定呢?实现方式有很多,最常见的有 3 类:
- 按业务类型分片
- 直接哈希分片
- 按地理位置分片
1、按业务类型分片
这种方案按应用的“业务类型”来划分。
举例:假设一共有 4 个应用,北京和上海机房都部署了这些应用。但应用 1、2 只在北京机房接入流量,在上海机房只是热备;应用 3、4 只在上海机房接入流量,在北京机房是热备。
这样一来,应用 1、2 的所有业务请求只会读写北京机房存储;应用 3、4 的所有请求只会读写上海机房存储。

按业务类型分片,也可以避免同一个用户修改同一条数据。
这里按业务类型在不同机房接入流量,还需要考虑多个应用之间的依赖关系。要尽可能把完成“相关”业务的应用部署在同一个机房,避免跨机房调用。
例如,订单、支付服务有依赖关系,会产生互相调用,那么这 2 个服务就应一起在 A 机房接入流量。社区、发帖服务有依赖关系,则在 B 机房接入流量。
2、直接哈希分片
这种方案下,最上层的路由层会根据用户 ID 计算“哈希”取模,然后从路由表中找到对应机房,再把请求转发到指定机房。
举例:一共 200 个用户,根据用户 ID 计算哈希值,再按路由规则把用户 1~100 路由到北京机房,用户 101~200 路由到上海机房。这样就能避免同一个用户修改同一条数据。

3、按地理位置分片
这种方案非常适合与地理位置密切相关的业务,例如打车、外卖服务。
以外卖为例,点外卖一定是“就近”点餐,整个业务范围中的商家、用户、骑手,通常都在相近的地理位置内。
针对这一特征,可以在最上层按用户的“地理位置”分片,分散到不同机房。
举例:北京、河北地区的用户点餐,请求只会打到北京机房;上海、浙江地区的用户,请求只会打到上海机房。这样的分片规则也能避免数据冲突。

提醒:这 3 种常见分片规则第一次看可能不好理解,建议配合图多理解几遍。搞懂这 3 个分片规则,才能真正明白怎么做异地多活。
总之,分片的核心思路在于:让同一个用户的相关请求,只在一个机房内完成所有业务“闭环”,不再出现“跨机房”访问。
阿里在实施这种方案时,给它起了个名字,叫作“单元化”。
当然,最上层路由层把用户分片后,理论上同一个用户只会落在同一个机房。但也不排除程序 Bug 导致用户请求在两个机房之间“漂移”。
安全起见,每个机房在写存储时还需要一套机制来检测“数据归属”。应用层操作存储时,要通过中间件做“兜底”,避免写入本不该属于本机房的数据。(篇幅限制,这里不展开,理解思路即可)
现在,两个机房就可以都接收“读写”流量(前提是请求已做好分片),底层存储保持“双向”同步,两个机房都拥有全量数据。任意机房故障时,另一个机房就可以“接管”全部流量,完成快速切换。
不仅如此,因为机房部署在异地,还可以进一步“优化”路由规则,让用户访问就近机房,整个系统的性能也会大幅提升。
还有一种无法做数据分片的情况:全局数据。例如系统配置、商品库存这类需要强一致的数据,这类服务仍只能采用写主机房、读从机房的方案,不做双活。
双活的重点,是优先保证“核心”业务先实现双活,而不是“全部”业务都实现双活。
至此,才算实现了真正的“异地双活”。
到这里也能看得出,完成这样一套架构,需要投入的成本是巨大的。
路由规则、路由转发、数据同步中间件、数据校验兜底策略,不仅需要开发强大的中间件,还要业务配合改造,包括业务边界划分、依赖拆分等一系列工作。没有足够的人力物力,这套架构很难落地。
11 异地多活
理解了异地双活,“异地多活”就很好理解了:在异地双活的基础上,部署多个机房即可。架构变成这样:

这些服务按“单元化”方式部署,可以让每个机房分布在任意地区,随时扩展新机房,只需要在最上层定义好分片规则即可。
但仍有一个小问题:随着机房越来越多,一个机房写入数据后,需要同步的机房也越来越多,实现复杂度会比较高。
因此业界又把这一架构进一步优化,从“网状”架构升级为“星状”架构:

这种方案必须设立一个“中心机房”。任意机房写入数据后,只同步到中心机房,再由中心机房同步到其它机房。
这样做的好处是:一个机房写入数据,只需要同步到中心机房即可,不需要关心总共部署了多少个机房,实现复杂度大大简化。
与此同时,中心机房的“稳定性”要求会比较高。不过即便中心机房发生故障,我们也可以把任意一个机房提升为中心机房,继续按原有架构提供服务。
至此,系统就彻底实现了“异地多活”。
多活的优势在于:可以任意扩展机房“就近”部署,任意机房故障都能快速“切换”,大幅提升系统可用性。同时,再也不用担心系统规模增长,因为这套架构具备极强的“扩展能力”。
从最简单的应用开始,一路优化到最终架构方案,现在你应该对异地多活有了完整的理解。
总结
这篇文章的重点总结如下:
- 优秀的软件架构应遵循高性能、高可用、易扩展 3 大原则。其中“高可用”在系统规模越来越大时,会变得尤为重要。
- 系统发生故障并不可怕,能以“最快”速度恢复才是高可用追求的目标,异地多活是实现高可用的有效手段。
- 提升高可用的核心是“冗余”。备份、主从副本、同城灾备、同城双活、两地三中心、异地双活、异地多活,本质都是在做冗余。
- 同城灾备分为“冷备”和“热备”。冷备只备份数据、不提供服务;热备实时同步数据,并做好随时切换的准备。
- 同城双活比灾备的优势在于:两个机房都能接入“读写”流量,既提高可用性,又提升系统性能。虽然物理上是两个机房,但逻辑上仍当作一个机房来用。
- 两地三中心是在同城双活的基础上,额外部署一个异地机房做“灾备”,用于抵御“城市”级别灾害,但启用灾备机房需要时间。
- 异地双活才是抵御“城市”级别灾害的更优方案。两个机房同时提供服务,故障随时可切换,可用性高,但实现也最复杂。理解了异地双活,才能真正理解异地多活。
- 异地多活是在异地双活的基础上任意扩展多个机房,既提高了可用性,又能应对更大规模流量压力,扩展性最强,是实现高可用的最终方案。
后记
这篇文章从“宏观”层面介绍了异地多活架构的核心思路,信息量较大。如果不好理解,建议多读几遍。
由于篇幅限制,很多细节没有展开。这篇文章更像是在讲异地多活的架构之“道”;而真正实施的“术”,要考虑的点其实非常多,需要开发强大的“基础设施”才能落地。
不仅如此,真正实现异地多活还需要遵循一些原则,例如业务梳理、业务分级、数据分类、数据最终一致性保障、机房切换一致性保障、异常处理等。同时,相关运维设施、监控体系也必须跟得上。
宏观上需要考虑业务(微服务部署、依赖、拆分、SDK、Web 框架)和基础设施(服务发现、流量调度、持续集成、同步中间件、自研存储);微观上要开发各种中间件,还要关注中间件的高性能、高可用、容错能力。复杂度之高,只有亲身参与过才能体会。
值得提醒的是,只有真正理解“异地双活”,才能彻底理解“异地多活”。从同城双活演进到异地双活的过程,是最为复杂的。最核心的东西包括:业务单元化划分、存储层数据双向同步、最上层的分片逻辑,这些是实现异地多活的重中之重。
希望这些架构经验,对你有所启发。