分布式系统架构设计:同城灾备、同城双活、两地三中心、异地多活等概念

具体的内容可以直接参考原文搞懂异地多活,看这篇就够了 | Kaito's Blog

下面是一些核心的知识点梳理:

一、系统可用性

一个好的软件架构应该遵循以下 3 个原则:高性能、高可用、易扩展

1、「高性能」意味着系统拥有更大流量的处理能力,更低的响应延迟。例如 1 秒可处理 10W 并发请求,接口响应时间 5 ms 等等。

2、「易扩展」表示系统在迭代新功能时,能以最小的代价去扩展,系统遇到流量压力时,可以在不改动代码的前提下,去扩容系统。

3、「高可用」这个概念,看起来很抽象,怎么理解它呢?通常用 2 个指标来衡量:

  • 平均故障间隔 MTBF(Mean Time Between Failure):表示两次故障的间隔时间,也就是系统「正常运行」的平均时间,这个时间越长,说明系统稳定性越高
  • 故障恢复时间 MTTR(Mean Time To Repair):表示系统发生故障后「恢复的时间」,这个值越小,故障对用户的影响越小

可用性与这两者的关系:可用性(Availability)= MTBF / (MTBF + MTTR) * 100%

这个公式得出的结果是一个「比例」,通常我们会用「N 个 9」来描述一个系统的可用性。

系统发生故障其实是不可避免的,尤其是规模越大的系统,发生问题的概率也越大。这些故障一般体现在 3 个方面:

  • 硬件故障:CPU、内存、磁盘、网卡、交换机、路由器
  • 软件问题:代码 Bug、版本迭代
  • 不可抗力:地震、水灾、火灾、战争

二、主从副本方案

1、「单机架构」与「备份」方案

「单机架构」有一个致命的缺点:一旦遭遇意外,例如磁盘损坏、操作系统异常、误删数据,那这意味着所有数据就全部「丢失」了,这个损失是巨大的。

于是很容易想到一个方案「备份」,就是对数据做备份,把数据库文件「定期」cp 到另一台机器上,这样,即使原机器丢失数据,你依旧可以通过备份把数据「恢复」回来,以此保证数据安全。这个方案实施起来虽然比较简单,但存在 2 个问题:

  • 恢复需要时间:业务需先停机,再恢复数据,停机时间取决于恢复的速度,恢复期间服务「不可用」
  • 数据不完整:因为是定期备份,数据肯定不是「最新」的,数据完整程度取决于备份的周期

很明显,数据库越大,意味故障恢复时间越久。那按照「高可用」标准,这个方案可能连 1 个 9 都达不到,远远无法满足我们对可用性的要求。

2、「主从副本」方案

更好的方案:「主从副本」方案,关键思路就是:冗余

可以在另一台机器上,再部署一个数据库实例,让这个新实例成为原实例的「副本」,让两者保持「实时同步」,就像这样:

我们一般把原实例叫作主库(master),新实例叫作从库(slave)。这个方案的优点在于:

  • 数据完整性高:主从副本实时同步,数据「差异」很小
  • 抗故障能力提升:主库有任何异常,从库可随时「切换」为主库,继续提供服务
  • 读性能提升:业务应用可直接读从库,分担主库「压力」读压力

 同样的思路,你的「业务应用」也可以在其它机器部署一份,避免单点。因为业务应用通常是「无状态」的(不像数据库那样存储数据),所以直接部署即可,非常简单。

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

3、「主从副本」方案解决不了的问题

按照前面的分析,为了避免单点故障,你的应用虽然部署了多台机器,但这些机器的分布情况,我们并没有去深究,按照服务的故障粒度区分:服务器 -> 机柜 -> 机房 -> 城市,「主从副本」方案解决不了机房级别的故障。

  • 机房出现故障的概率非常低,可能职业生涯都不会遇到过一次不同体量的系统,它们各自关注的重点是什么?
  • 体量很小的系统,它会重点关注「用户」规模、增长,这个阶段获取用户是一切。等用户体量上来了,这个阶段会重点关注「性能」,优化接口响应时间、页面打开速度等等,这个阶段更多是关注用户体验。

等体量再大到一定规模后你会发现,「可用性」就变得尤为重要。像微信、支付宝这种全民级的应用,如果机房发生一次故障,那整个影响范围可以说是非常巨大的。

那到底该怎么应对机房级别的故障呢?「同城灾备」方案。

三、「同城灾备」方案

思路还是「冗余」,想要抵御「机房」级别的风险,那应对方案就不能局限在一个机房内了。你可以在「同一个城市」再搭建一个机房,原机房我们叫作 A 机房,新机房叫 B 机房,这两个机房的网络用一条「专线」连通。

有了新机房,怎么把它用起来呢?这里还是要优先考虑「数据」风险。

为了避免 A 机房故障导致数据丢失,所以我们需要把数据在 B 机房也存一份。最简单的方案还是和前面提到的一样:备份

【冷备】:

A 机房的数据,定时在 B 机房做备份(拷贝数据文件),这样即使整个 A 机房遭到严重的损坏,B 机房的数据不会丢,通过备份可以把数据「恢复」回来,重启服务。

为什么叫冷备呢?因为 B 机房只做备份,不提供实时服务,它是冷的,只会在 A 机房故障时才会启用。

【热备】:

B 机房几乎「镜像」了一份 A 机房的所有东西,从最上层的接入层,到中间的业务应用,到最下层的存储。两个机房唯一的区别是,A 机房的存储都是主库,而 B 机房都是从库

热的意思是指,B 机房处于「待命」状态,A 故障后 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 机房实时接入了流量,还能应对随时的故障切换,这种方案我们把它叫做「同城双活」。

因为两个机房都能处理业务请求,这对我们系统的内部维护、改造、升级提供了更多的可实施空间(流量随时切换),现在,整个系统的弹性也变大了,是不是更爽了?

这种方案也不是万能的。

五、「两地三中心」方案

上面我们把两个机房当作一个整体来规划,但这2个机房在屋里层面上,还是处于一个城市内,如果是整个城市发生自然灾害,例如:地震、水灾,那2个机房依然存在「全局覆没」的风险。

没办法,继续冗余,需要异地部署。

通常建议两个机房的距离要在 1000 公里以上,这样才能应对城市级别的灾难。假设之前的 A、B 机房在北京,那这次新部署的 C 机房可以放在上海。

把 C 机房用起来,最简单粗暴的方案还就是做「冷备」,即定时把 A、B 机房的数据,在 C 机房做备份,防止数据丢失。

这种方案,就是我们经常听到的「两地三中心」。

两地是指 2 个城市,三中心是指有 3 个机房,其中 2 个机房在同一个城市,并且同时提供服务,第 3 个机房部署在异地,只做数据灾备。

这种架构方案,通常用在银行、金融、政企相关的项目中。它的问题还是前面所说的,启用灾备机房需要时间,而且启用后的服务,不确定能否如期工作。

所以,要想真正的抵御城市级别的故障,越来越多的互联网公司,开始实施「异地双活」。

六、「伪异地双活」方案

那异地双活是不是直接「照搬」同城双活的模式去部署就可以了呢?比如下面这样?

 可以看到,北京到上海,两个机房的网络是通过「跨城专线」连通的。

此时两个机房都接入流量,那上海机房的请求,可能要去读写北京机房的存储,这里存在一个很大的问题:网络延迟

因为两个机房距离较远,受到物理距离的限制,现在,两地之间的网络延迟就变成了「不可忽视」的因素了。

北京到上海的距离大约 1300 公里,即使架设一条高速的「网络专线」,光纤以光速传输,一个来回也需要近 10ms 的延迟。

况且,网络线路之间还会经历各种路由器、交换机等网络设备,实际延迟可能会达到 30ms ~ 100ms,如果网络发生抖动,延迟甚至会达到 1 秒。

不止是延迟,远距离的网络专线质量,是远远达不到机房内网络质量的,专线网络经常会发生延迟、丢包、甚至中断的情况。总之,不能过度信任和依赖「跨城专线」。

你可能会问,这点延迟对业务影响很大吗?影响非常大!

试想,一个客户端请求打到上海机房,上海机房要去读写北京机房的存储,一次跨机房访问延迟就达到了 30ms,这大致是机房内网网络(0.5 ms)访问速度的 60 倍(30ms / 0.5ms),一次请求慢 60 倍,来回往返就要慢 100 倍以上。

而我们在 App 打开一个页面,可能会访问后端几十个 API,每次都跨机房访问,整个页面的响应延迟有可能就达到了秒级,这个性能简直惨不忍睹,难以接受。

看到了么,虽然我们只是简单的把机房部署在了「异地」,但「同城双活」的架构模型,在这里就不适用了,还是按照这种方式部署,这是「伪异地双活」!

那如何做到真正的异地双活呢?

七、「真正的异地双活」方案

既然「跨机房」调用延迟是不容忽视的因素,那我们只能尽量避免跨机房「调用」,规避这个延迟问题。

也就是说,上海机房的应用,不能再「跨机房」去读写北京机房的存储,只允许读写上海本地的存储,实现「就近访问」,这样才能避免延迟问题。

还是之前提到的问题:上海机房存储都是从库,不允许写入啊,除非我们只允许上海机房接入「读流量」,不接收「写流量」,否则无法满足不再跨机房的要求。

很显然,只让上海机房接收读流量的方案不现实,因为很少有项目是只有读流量,没有写流量的。所以这种方案还是不行,这怎么办?

此时,你就必须在存储层做改造了。

要想上海机房读写本机房的存储,那上海机房的存储不能再是北京机房的从库,而是也要变为「主库」。

你没看错,两个机房的存储必须都是「主库」,而且两个机房的数据还要「互相同步」数据,即客户端无论写哪一个机房,都能把这条数据同步到另一个机房。

因为只有两个机房都拥有「全量数据」,才能支持任意切换机房,持续提供服务。

怎么实现这种「双主」架构呢?它们之间如何互相同步数据?

如果你对 MySQL 有所了解,MySQL 本身就提供了双主架构,它支持双向复制数据,但平时用的并不多。而且 Redis、MongoDB 等数据库并没有提供这个功能,所以,你必须开发对应的「数据同步中间件」来实现双向同步的功能。

此外,除了数据库这种有状态的软件之外,你的项目通常还会使用到消息队列,例如 RabbitMQ、Kafka,这些也是有状态的服务,所以它们也需要开发双向同步的中间件,支持任意机房写入数据,同步至另一个机房。

看到了么,这一下子复杂度就上来了,单单针对每个数据库、队列开发同步中间件,就需要投入很大精力了。

业界也开源出了很多数据同步中间件,例如阿里的 Canal、RedisShake、MongoShake,可分别在两个机房同步 MySQL、Redis、MongoDB 数据

很多有能力的公司,也会采用自研同步中间件的方式来做,例如饿了么、携程、美团都开发了自己的同步中间件。

现在,整个架构就变成了这样:

两个机房的存储层都互相同步数据的。有了数据同步中间件,就可以达到这样的效果:

  • 北京机房写入 X = 1
  • 上海机房写入 Y = 2
  • 数据通过中间件双向同步
  • 北京、上海机房都有 X = 1、Y = 2 的数据

也就是说,在很短的时间内,同一个用户修改同一条数据,两个机房无法确认谁先谁后,数据发生「冲突」。

这是一个很严重的问题,系统发生故障并不可怕,可怕的是数据发生「错误」,因为修正数据的成本太高了。我们一定要避免这种情况的发生。解决这个问题,有 2 个方案。

第一个方案,数据同步中间件要有自动「合并」数据、解决「冲突」的能力。

这个方案实现起来比较复杂,要想合并数据,就必须要区分出「先后」顺序。我们很容易想到的方案,就是以「时间」为标尺,以「后到达」的请求为准。

但这种方案需要两个机房的「时钟」严格保持一致才行,否则很容易出现问题。例如:

  • 第 1 个请求落到北京机房,北京机房时钟是 10:01,修改 X = 1
  • 第 2 个请求落到上海机房,上海机房时钟是 10:00,修改 X = 2

因为北京机房的时间「更晚」,那最终结果就会是 X = 1。但这里其实应该以第 2 个请求为准,X = 2 才对。

可见,完全「依赖」时钟的冲突解决方案,不太严谨。

所以,通常会采用第二种方案从「源头」就避免数据冲突的发生

从源头避免数据冲突的思路是:在最上层接入流量时,就不要让冲突的情况发生

具体来讲就是,要在最上层就把用户「区分」开,部分用户请求固定打到北京机房,其它用户请求固定打到上海 机房,进入某个机房的用户请求,之后的所有业务操作,都在这一个机房内完成,从根源上避免「跨机房」。

所以这时,你需要在接入层之上,再部署一个「路由层」(通常部署在云服务器上),自己可以配置路由规则,把用户「分流」到不同的机房内。

但这个路由规则,具体怎么定呢?有很多种实现方式,最常见的我总结了 3 类:

  1. 按业务类型分片
  2. 直接哈希分片
  3. 按地理位置分片

具体的可以查看原文,总之,分片的核心思路在于,让同一个用户的相关请求,只在一个机房内完成所有业务「闭环」,不再出现「跨机房」访问

这里还有一种情况,是无法做数据分片的:全局数据。例如系统配置、商品库存这类需要强一致的数据,这类服务依旧只能采用写主机房,读从机房的方案,不做双活。

双活的重点,是要优先保证「核心」业务先实现双活,并不是「全部」业务实现双活。

至此,我们才算实现了真正的「异地双活」!

到这里你可以看出,完成这样一套架构,需要投入的成本是巨大的

路由规则、路由转发、数据同步中间件、数据校验兜底策略,不仅需要开发强大的中间件,同时还要业务配合改造(业务边界划分、依赖拆分)等一些列工作,没有足够的人力物力,这套架构很难实施。

八、「异地多活」方案

这种方案需要设立一个「中心机房」,任意机房写入数据后,都只同步到中心机房,再由中心机房同步至其它机房。

这样做的好处是,一个机房写入数据,只需要同步数据到中心机房即可,不需要再关心一共部署了多少个机房,实现复杂度大大「简化」。

但与此同时,这个中心机房的「稳定性」要求会比较高。不过也还好,即使中心机房发生故障,我们也可以把任意一个机房,提升为中心机房,继续按照之前的架构提供服务。

 多活的优势在于,可以任意扩展机房「就近」部署。任意机房发生故障,可以完成快速「切换」,大大提高了系统的可用性。

我们也再也不用担心系统规模的增长,因为这套架构具有极强的「扩展能力」。

九、小结

1、一个好的软件架构,应该遵循高性能、高可用、易扩展 3 大原则,其中「高可用」在系统规模变得越来越大时,变得尤为重要

2、系统发生故障并不可怕,能以「最快」的速度恢复,才是高可用追求的目标,异地多活是实现高可用的有效手段

3、提升高可用的核心是「冗余」,备份、主从副本、同城灾备、同城双活、两地三中心、异地双活,异地多活都是在做冗余

4、同城灾备分为「冷备」和「热备」,冷备只备份数据,不提供服务,热备实时同步数据,并做好随时切换的准备

5、同城双活比灾备的优势在于,两个机房都可以接入「读写」流量,提高可用性的同时,还提升了系统性能。虽然物理上是两个机房,但「逻辑」上还是当做一个机房来用

6、两地三中心是在同城双活的基础上,额外部署一个异地机房做「灾备」,用来抵御「城市」级别的灾害,但启用灾备机房需要时间

7、异地双活才是抵御「城市」级别灾害的更好方案,两个机房同时提供服务,故障随时可切换,可用性高。但实现也最复杂,理解了异地双活,才能彻底理解异地多活

8、异地多活是在异地双活的基础上,任意扩展多个机房,不仅又提高了可用性,还能应对更大规模的流量的压力,扩展性最强,是实现高可用的最终方案

十 、问题

1、为什么做灾备会浪费很多钱?机器在用,后续做多活还是那些机器,那浪费的钱去哪了?

几方面原因:

(1)做两次耗费的人财物 + 时间,包括中间为多活 + 非多活并行预留的 buffer、两次切换带来的风险等;

(2)即使只考虑机器硬件成本,也有 ROI 问题,因为从灾备上线到下线这段时间,灾备发挥作用很小(几乎可以认为只是 data backup),真出现紧急情况,切或不切的风险孰大孰小,真不好说;

(3)即使只考虑机器硬件成本,多活的规模自由、伸缩自由就能体现出来。举个例子:2单元多活的硬件成本确实和灾备差不多(100%冗余),但3单元、4单元呢?虽然多活单元规模也不是越多越省钱(非简单线性下降,和多活设计相关,细节不展开了),但至少留出了规模自由、伸缩自由空间。

2、异地多活这么难?

如果按场景分,异地多活从难到易,依次是:实时交易 -> 非实时交易 -> 社交类 -> 内容类 -> 其他。游戏类不好说,需要具体情况具体分析。

3、为什么要全部系统都多活, 为了什么呢?饿了么物流不多活也没什么影响吧?

物流和其他系统都基于统一基础设施(中间件、运维、工具、相关团队等),在基础设施进行多活改造后,除了切换并行期不得不同时支持多活、非多活及混合架构,后续所有统一基础设施(包括相关团队)将只支持多活架构。

  • 27
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值