数据恢复咨询热线:400-666-3702  

欢迎访问南京兆柏数据恢复公司,专业数据恢复15年

兆柏数据恢复公司

 常见问题

 当前位置: 主页 > 常见问题

软件系统的皇冠明珠--异地多活

浏览量: 次 发布日期:2023-09-07 23:26:09

软件系统的皇冠明珠--异地多活

  近期,珠海举办的“第十四届中国航展”,作为一名业余军迷,看到祖国航空航天领域不论是民用还是军用,都出现世界一流产品而感到骄傲和自豪。航空发动机被称为“工业之花”,也被誉为“工业皇冠上的明珠”。软件系统领域的“皇冠明珠”则是异地多活,那什么是异地多活呢?它解决了什么问题,又带来哪些挑战,工程师们又是如何解决挑战呢?

  软件系统的异地多活其实就是字面意思,让软件系统在不同的“地方”活着,他是为了提高系统的可用性。任何一个软件产品,不管宣传他有多么好的体验(经常不可用也不可能有好的体验)、多么优秀的功能、多么牛逼的性能,如果没有比较高的可用性那都是0,可用性就是那个“1”,而异地多活是为了提高可用性。

  另外,异地多活的理论依据是CAP中的AP方案,对一致性做了取舍。

  CAP:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

  在介绍系统可用性之前,我们先从系统架构设计原则说起。软件系统发展到现在,他的复杂度越来越高,人们的要求也随之变高,一个好的系统架构应该符合几个原则:

  高性能

  可扩展

  高可用

  这三个原则,高性能的概念是最为人所知的,不过他和可扩展性不是本文讨论的范围。推荐下表的IT系统性能量化指标:

  数据来源:Jeff Dean

  高可用(High Availability,简称HA)就是指系统可用性,通过异地多活去实现。在介绍可用性之前,先介绍两个概念:

  MTBF(Mean Time Between Failure):上次故障到这次故障的时间长度,值越大,说明系统稳定性越高。

  MTTR(Mean Time To Repair):发生故障到系统恢复正常的时间长度,值越小,说明系统影响越小。

  可用性和两者关系,可以用下面公式表示:

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

  这个公式得到的结果我们称之为可用性,通常情况下,我们使用几个九来表示系统的可用性。一般的互联网应用业务系统至少要做到4个9以上,笔者曾工作过的某金融企业核心系统是要求到达5个9的可用性。

  从可用性公式和上述表格中,只有故障间隔越长和恢复时间越短,系统可用性才会越高。当应用系统的规模足够复杂时,故障是一定会发生的,工程师的工作是尽量的增大 MTBF 和减小 MTTR 。软件系统千奇百怪的故障可以总结为三个方面:

  1、软件问题:操作系统故障,软件版本迭代、发布带来的代码Bug

  2、硬件问题:CPU、内存、硬盘、网卡、路由器、交换机

  3、外部问题:机房机柜的风、火、水、电以及城市网络

  我们无法保证每个服务器上每个硬件不损坏,也无法保障服务器运行的环境永远可靠不出故障(城市的建设可能会挖断光缆,机房可能会发生火灾,甚至城市可能会发生地震、战争)。故障并不可怕,只需要在最短时间恢复甚至让用户没有感知。为了提高可用性,无数工程师前辈为之探索,本文将介绍从最简单的单机架构到异地多活的演变之路。

  题外话:

  软件应用系统的后端服务,按照状态可以分为两大类:有状态的存储(数据库)和无状态应用层。对于无状态的应用来说,提高可用性相对比较简单。常见的方法,有通过硬件(F5)、软件(SLB、DNS)来实现负载均衡(Load Balancer)。本文说的可用性,专指对有状态的数据存储层,这也是在软件工程中相对比较难的部分。另外,有些系统在应用层的内存也存在部分有状态的数据,本文不涉及讨论这一部分有状态数据的异地多活。

  早期互联网公司用户少、规模小,相应的应用系统架构就简单,通常是这样的:

  它非常简单,应用读写数据库返回结果。此时的系统架构里,数据库还是单机部署的,因此有致命的缺点,一旦系统出现故障,例如硬盘损坏、操作系统异常或者服务器异常,就有可能出现数据全部丢失问题,丢失数据对现代企业来说是无法承受的。

  那如何避免这个问题呢?备份--有备无患,定时对数据库进行全量备份,然后将备份数据异地存储。这样即使原来服务器出现异常,也可以利用备份文件恢复数据。这就是早期互联网生产系统的架构,一个简单的应用和数据库架构,再加上一个备份。

  然而随着业务的发展规模的扩大数据的增加,靠备份恢复的架构带来挑战:

  1、恢复需要时间。

  2、恢复的数据完整性不高。

  首先只能数据恢复到备份点,其次如果备份数据很大,备份文件的拷贝和恢复也需要时间。这样可用性公式中MTTR值就不会小,可用性就低。那如何提高可用性呢?既然“冷备”无法解决数据完整性和快速恢复,那就用“热备”——主从架构(Master/Slave)。

  早期的单机+备份架构带来的问题,通过主从架构可以解决一部分。

  主从架构的方案,优点在于:

  数据完整性高:主从实时同步,有效降低数据不一致情况

  抗故障能力提升:主库有任何异常,从库可比较快速的升为主库,继续提供服务

  说明:

  数据库主从节点之间的同步方式会带来较大的系统性能性能变化。常见的同步方式分为强同步、异步同步和半同步。强同步是指从节点写入完成后,主节点才给应用返回数据。半同步是指有一个从节点收到消息,主节点给应用返回数据。异步同步等同于单机,不需要等待从节点的信息。

  这个方案不错,对比单机已显著提高数据库的可用性。美中不足的是该架构的从库(Slave)只作为备份而存在,从资源利用率看并没有充分使用。再稍加改造,将应用一部分的读业务从主库迁移到从库上。

  无状态的应用层也需要避免单点问题,部署相同的多个应用,这就需要在应用层前加上接入层(accesss)。到此,这套主从读写分离高可用架构,不仅提高了系统的可用性,还提升资源利用率和系统吞吐量,看上去这个架构似乎已经是完美 了,确实这套架构已经是比较主流的架构了。

  主从架构是解决服务器层的问题。当一个服务器出现问题,系统可以快速切换到该机房的其他服务器,从而提高了系统可用性。我们从最开始的单个系统上的故障扩大到服务器,以此类推,还有机柜、机房甚至城市也会出现故障,这些出现故障上述架构是无法解决的。

  有同学会说,至于这么夸张吗?我们先看几个2022年的新闻:

  1、长沙电信大楼起火,海量数据如何保全?

  2、韩版腾讯的一场大火,让韩国沦陷了整整4天

  3、UPS 爆炸:菲律宾一数据中心失火

  再看更久的旧闻:

  1、2015 年 5 月,支付宝支付失灵:电缆惹的祸?

  2、2016年 9 月,阿里云北京机房内网故障,大面积服务异常

  3、年终盘点 | 2020云巨头们的宕机事件

  4、2021 年 7 月,B站回应深夜“崩了”:部分服务器机房发生故障,对不起!

  5、2021 年 7 月,河南暴雨致使机房停电,多家网站陷入瘫痪

  可见,机房、市政也不是永远可靠的,虽然出现的问题概率很小,一旦发生,影响可能是致命的。机房出现的概率也太小了,工作这么多年了还没有听说身边发生过这种事,有必要考虑这种事情吗?如果是一线程序员这么看问题可以理解,如果一家公司的CEO、CIO、架构师们也是这么想的,那要么是公司业务刚起步,否则笔者建议趁早换公司。

  软件系统在不同阶段,他的侧重点是不一样的。互联网蓬勃发展时代,业务刚开始时,用户规模和增长速度是最关键指标。等有一定用户规模,性能就变得尤其重要。而当用户规模更大时,可用性就变得不能举足轻重了。大家可以想象下,像微信、支付宝这种国民现象级的产品,如果突然长时间无法提供服务将会怎样

  因此,对于有些系统来说再小概率的风险,也是不能忽视的,跨机房的高可用架构就应运而生。同城高可用是从灾备演化的,将存储层的数据每天离线备份到同城的机房。这虽然解决了核心机房出现重大故障,数据还能找回来问题。但这种架构和单机机一样,数据恢复的实效性和完整性是比较差的,可用性并没有很大的提升。

  借鉴在单机房中“主从”架构的思路,我们比较容易得到比较初级的同城灾备的架构,在机房A部署一套完整的系统,在机房B部署一套数据库系统,将机房A的数据单向同步到机房B的数据库中。实现数据实时保存在同一个城市的不同机房上,这样在数据安全性有了很大的提升。但是系统的可用性,不是指数据存在而是在指系统可用,显然这个架构并不符合系统可用。

  我们可以想象下,当机房A出现问题时,最理想状态要怎样才能把这个系统重新启动呢?

  1、在机房B部署应用,并成功启动。

  2、在机房B部署接入层,配置负载均衡。

  3、断开“专线单向同步”,将机房B数据库升级为Master。

  4、域名指向B机房的接入层,接入流量,业务恢复

  在最理想状态下,当机房A出现问题时,机房B需要做上述的事情,在做完这些事情之前,整个系统是不可用的,所以这个架构对于系统可用性并不是很高。因此,想要在机房级提高可用性,最好是机房B本身就有应用层和接入层的环境,真正实现“若有战、召必回、战必胜”。我们就需要再次改进架构图。

  到这里,就算整个机房A宕机,我们只需要做 这2 件事即可:

  1、 断开“专线单向同步”(如果需要),将机房B数据库升级为Master

  2、域名指向机房B接入层,接入流量,业务恢复

  这样一来,系统恢复速度快了很多,也提高了系统可用性。机房B几乎复制机房A的所有模块,从最上层的接入层,到中间的业务应用层,到最下层的存储。两个机房最大区别是,机房A的数据是由业务实际产生的,而机房B的数据则是由A同步过来的。

  到这里有同学会问,平时机房B不真正干活,关键时刻能起作用?通过笔者过往经验来看,大概率是不能起作用。毕竟是驴是马要拉出来溜溜才知道,所以我们的架构还要再演进。另外,这套架构在资源利用率上也不是最佳选择,所以我们很自然的想到,如果机房B平时也能干活就好了。

  为了让机房B平时正儿八经的干活,时刻保持着战斗状态。于是我们的架构演进如下:

  因为机房B的数据只能是从机房A单向同步过来,所以机房B只能接受读的任务。同城的机房,一般空间距离是几十公里,所以他的跨机房数据交流还是可以接受的。这样机房B就有了一个系统的读/写完全的实战能力。到此,这个系统理论上应该足够强大了。

  正如前面我们看到的新闻,一个城市有可能发生自燃灾难或者是战争,所以机房在一个城市并不是终极目标,同城的“异地”还不够“异”,于是跨城市的异地多活架构应运而生。

  我们还是以机房A和B举例,本来是都在一个城市,那当一个城市出现灾难时(比如去年郑州水灾),显然同城两机房架构。所以需要分开城市部署,例如把机房A放在杭州,机房B放在北京。那不同城市的异地双活和同城是不是照猫画虎,答案是否定的。

  我们再看一遍同城双活的架构图,他们是有跨机房的数据交互,而当两个机房物理位置超过1000km时,即使以光速传输,一个来回也需要近 10ms 左右的延迟。实际上,考虑到跨城市的网络需要经历更多路由器、交换机等网络设备,基本上在几十甚至上百毫秒的延迟。

  或许有同学会问,这点延迟对业务影响很大吗?答案是:很大。我们想象下这样一个场景,某用户的请求发到杭州机房,杭州机房要去读写北京机房的存储,一次跨机房网络访问延迟就达到了 50ms左右,这大致是机房内网网络(0.5 ms)访问速度的 100 倍,这样一次请求慢 100 倍,而通常用户在 App 上的一个操作可能会访问后端十几个网络请求,每次都跨机房访问,这个性能怕是用户要卸载App了。

  因此,如果我们只是简单的把机房部署在异地,这种异地多活是不可用的。那如何做到真正的异地双活呢?

  异地双活中,跨机房的数据交互是不可用的,我们就尽量避免跨机房的访问,让本机房的服务只访问本机房的资源。但是按照主从架构,机房B是从库,不能有业务直接写入,否则系统数据就会紊乱。所以机房B只能有读访问的架构方案是不现实,因为很少有系统只有读没有写。所以必须在存储层改造了。于是我们系统的架构变成这样:

  两个机房的存储都是主库,而且两个机房的数据双相同步数据,用户与任意机房的请求数据都能同步到另一个机房,两个机房都拥有全量数据,就能支持应用任意切换机房。

  但是这里会遇到大量的问题,不仅是数据库的数据(MySQL、MongoDB等)双向同步的需求,还有MQ、Kafka、ZK、Redis等等组件,所以通常需要中间件去完成这些工作。这样就不用再担心专线问题,因为即使专线偶尔出问题,通过中间件最终实现数据一致。

  到了这一步,软件系统的可用性可以认为完美了吗?答案:还没有。。。因为两个机房都可以完全独立的完成全部业务,假设某个用户几乎同时发起两个请求并且是操作同一行数据,就有概率发生冲突。我们可以想象下这样的场景:

  某银行在多个平台上提供借钱服务器的通道,用户小王几乎同时在某音和某手上发起借钱,这两条请求最终都会请求到银行两个机房。

  1、小王两个请求都是修改在某银行余额信息。

  2、其中某音的请求是借款1000元,将小王在该银行的余额更新为1000。而此时未同步到另一个机房数据库。

  3、某手的的请求是借款2000元,将小王在该银行的余额更新为2000。此时也未同步到另一个机房数据库。

  4、中间件该怎么去同步到对方数据库?

  这就是同一个用户在短时间内有2个请求并且是分给两个机房去修改同一条数据的场景,这种场景下就有可能会出现数据错误,系统发生故障已经很可怕,但更可怕的用户数据错误。

  那这个问题怎么解决呢?目前业界常见的做法是在增加路由层,在路由层配置路由规则,将符合规则的用户分配到相应的机房。这也有多种实现方式,总结了常见的两大类方法:

  按用户GIS地理路由

  按用户ID路由

  地理GIS

  在一些互联网业务和地理GIS信息关系密切,比如外卖、打车等业务。因为当用户在这些业务场景时,不可能在较短时间内同时出现在杭州和北京。这样就可以路由层制定规则,比如把杭州的用户请求就发到杭州机房,北京的用户请求发到北京的机房。这样的路由分配规则,就能解决数据冲突的问题。

  用户ID

  另一种是根据用户ID路由。当路由层接收到用户请求时,将用户ID通过一定的算法计算取模后,分配到对应的机房。这样就能保证同一个用户发起的请求一定在一个机房内。

  总之,路由层的是为了解决在短时间内,具有相同规则属性的数据是在一个机房内完成所有业务,两个机房同时修改一行数据的情况。后来有公司把这个模型架构叫做单元化。到此,真正机房级的FailOver做到了,不同城市的不同机房可以完成所有业务,底层通过中间件做好数据双向同步,当一个城市的机房出现问题,另一个城市的机房可以顶上。

  到异地双活的架构是笔者亲身参与过的项目,业内还有异地多活、全球机房等架构模型,这些笔者没有经验可谈了。这篇小文不是实战,也不是操作手册,而是在架构层面介绍异地多活的一些思路,算是工作10余年的个人总结。

  其实到了同城双活的系统架构已经极其复杂,需要一个专业架构师团队,负责数据同步、读写分离的业务改造、数据一致性校验等等工作,这些不仅需要强大的中间件团队,同时还要对业务有敏锐的观察力,如业务边界划分、依赖拆分、数据血缘等等一系列工作。而从同城双活演变到异地双活,则又是一次数量级难度的提升。

  最后,总结下这篇小文的几个观点:

  公司业务进入稳定期,系统可用性是所有指标的前面那个1,不考虑系统稳定性,谈系统的功能、性能、扩展性都是耍流氓。

  服务器总是会出问题的,机房也有可能发生地震、火灾、水灾,系统在面对异常面前,需要在最短时间内恢复正常,异地多活是最有效方法。

  多活是通过多副本实现的,从最开始的离线文件备份、数据库备份、同城备份、异地备份,都是通过副本实现。

  同城双活本质还是读写分离的一种演变架构,但需要对跨机房业务请求的时延有充分的认识。

  到了异地双活一定不能出现跨机房访问和同一条数据有可能在短时间内被两个机房的请求更新。

相关推荐