网站高可用架构设计——异地多活与容灾

 从公众号转载,关注微信公众号掌握更多技术动态

---------------------------------------------------------------

一、异地多活架构简介

1.什么是异地多活

无论是高可用计算架构,还是高可用存储架构,其本质的设计目的都是为了解决部分服务器故障的场景下,如何保证系统能够继续提供服务。但在一些极端场景下,有可能所有服务器都出现故障。例如,典型的有机房断电、机房火灾、地震、水灾……这些极端情况会导致某个系统所有服务器都故障,或者业务整体瘫痪,而且即使有其他地区的备份,把备份业务系统全部恢复到能够正常提供业务,花费的时间也比较长,可能是半小时,也可能是 12 小时。因为备份系统平时不对外提供服务,可能会存在很多隐藏的问题没有发现。如果业务期望达到即使在此类灾难性故障的情况下,业务也不受影响,或者在几分钟内就能够很快恢复,那么就需要设计异地多活架构。任意一个点挂了,也可以迅速切换到其他节点对外服务,节点之间的数据做到准实时同步。

2.应用场景

(1)是否需要异地多活的标准

顾名思义,异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。判断一个系统是否符合异地多活,需要满足两个标准:

  • 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。

  • 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。

(2)异地多活的复杂性——业务量不大,没必要做异地多活

  • 系统复杂度会发生质的变化,需要设计复杂的异地多活架构。

  • 成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。

  • 同城专线跨机房读取数据,存在几毫秒级延迟,异地跨机房读取数据,存在几十毫秒额延迟

(3)何时选择异地多活

  • 异地容灾:一地机房故障可以快速切换到另一个机房

  • 动态加速:将用户请求发放到距离进的机房

  • 流量均衡:环境一个机房带来的流量压力

异地多活虽然功能很强大,但也不是都需要。例如,常见的新闻网站、企业内部的 IT 系统、游戏、博客站点等,如果无法承受异地多活带来的复杂度和成本,是可以不做异地多活的,只需要做异地备份即可。因为这类业务系统即使中断,对用户的影响并不会很大,例如,A 新闻网站看不了,用户换个新闻网站即可。而共享单车、滴滴出行、支付宝、微信这类业务,就需要做异地多活了,这类业务系统中断后,对用户的影响很大。例如,支付宝用不了,就没法买东西了;滴滴用不了,用户就打不到车了。

当然,如果业务规模很大,能够做异地多活的情况下还是尽量。首先,这样能够在异常的场景下给用户提供更好的体验;其次,业务规模很大肯定会伴随衍生的收入,例如广告收入,异地多活能够减少异常场景带来的收入损失。同样以新闻网站为例,虽然从业务的角度来看,新闻类网站对用户影响不大,反正用户也可以从其他地方看到基本相同的新闻,甚至用户几个小时不看新闻也没什么问题。但是从网站本身来看,几个小时不可访问肯定会影响用户对网站的口碑;其次几个小时不可访问,网站上的广告收入损失也会很大。

二、架构模式

制定多机房部署的方案不是一蹴而就的,而是不断迭代发展的

1. 同城异区

(1)同城异区简介

同城异区指的是将业务部署在同一个城市不同区的多个机房。例如,在北京部署两个机房,一个机房在海淀区,一个在通州区,然后将两个机房用专用的高速网络连接在一起。

同城的两个机房,距离上一般大约就是几十千米,通过搭建高速的网络,同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度。这就意味着虽然是两个不同地理位置上的机房,但逻辑上我们可以将它们看作同一个机房,这样的设计大大降低了复杂度,减少了异地多活的设计和实现复杂度及成本。

然而同城异区架构,无法应对城市级别的大停电或者大灾害。但是这种极端情况发生概率是比较低的。其次,除了这类灾难,机房火灾、机房停电、机房空调故障这类问题发生的概率更高,而且破坏力一样很大。而这些故障场景,同城异区架构都可以很好地解决。因此,结合复杂度、成本、故障发生概率来综合考虑,同城异区是应对机房级别故障的最优架构。

(2)同城异区设计

假设这样的场景:在北京有 A 和 B 两个机房,A 是联通的机房,B 是电信的机房,机房之间以专线连接,方案设计时,核心思想是,尽量避免跨机房的调用。具体方案如下:

首先,数据库的主库可以部署在一个机房中,比如部署在 A 机房中,那么 A 和 B 机房数据都会被写入到 A 机房中。然后,在 A、B 两个机房中各部署一个从库,通过主从复制 的方式,从主库中同步数据,这样双机房的查询请求可以查询本机房的从库。一旦 A 机 房发生故障,可以通过主从切换的方式,将 B 机房的从库提升为主库,达到容灾的目的。

缓存也可以部署在两个机房中,查询请求也读取本机房的缓存,如果缓存中数据不存 在,就穿透到本机房的从库中,加载数据。数据的更新可以更新双机房中的数据,保证 数据的一致性。不同机房的 RPC 服务会向注册中心,注册不同的服务组,而不同机房的 RPC 客户端, 也就是 Web 服务,只订阅同机房的 RPC 服务组,这样就可以实现 RPC 调用尽量发生 在本机房内,避免跨机房的 RPC 调用。

图片

(3)同城异区不同模式

①同城灾备

图片

  • 冷备: B 机房只做备份,不提供实时服务,它是冷的,只会在 A 机房故障时才会启用。A 机房的数据,定时在 B 机房做备份(拷贝数据文件),这样即使整个 A 机房遭到严重的损坏,B 机房的数据不会丢,通过备份可以把数据「恢复」回来,重启服务。但是这样可能导致数据不完整、恢复数据期间业务不可用,整个系统的可用性还是无法得到保证。

  • 热备:B 机房「镜像」一份 A 机房的所有东西,从最上层的接入层,到中间的业务应用,到最下层的存储。两个机房唯一的区别是,A 机房的存储都是主库,而 B 机房都是从库。热备相比于冷备最大的优点是:随时可切换。

②同城双活

图片

A 机房挂掉,全部流量切到 B 机房,B 机房能否真的如我们所愿,正常提供服务?在一个机房内部署服务,还总是发生各种各样的问题,例如:发布应用的版本不一致、系统资源不足、操作系统参数不一样等等。多部署一个机房,这些问题只会增多,不会减少。另外,从「成本」的角度来看,新部署一个机房,需要购买服务器、内存、硬盘、带宽资源,花费成本也是非常高昂的,只让它当一个后备,那么十分浪费资源

因此,需要让 B 机房也接入流量,实时提供服务,这样做的好处,一是可以实时训练这支后备军,让它达到与 A 机房相同的作战水平,随时可切换,二是 B 机房接入流量后,可以分担 A 机房的流量压力。把 B 机房的接入层 IP 地址,加入到 DNS 中,这样,B 机房从上层就可以有流量进来了。

但此时B 机房的存储,现在可都是 A 机房的「从库」,从库默认可都是「不可写」的,B 机房的写请求打到本机房存储上,肯定会报错,这还是不符合预期。怎么办?这时,就需要在「业务应用」层做改造了。

业务应用在操作数据库时,需要区分「读写分离」(一般用中间件实现),即两个机房的「读」流量,可以读任意机房的存储,但「写」流量,只允许写 A 机房,因为主库在 A 机房。

这会涉及到你用的所有存储,例如项目中用到了 MySQL、Redis、MongoDB 等等,操作这些数据库,都需要区分读写请求,所以这块需要一定的业务「改造」成本。

因为 A 机房的存储都是主库,所以我们把 A 机房叫做「主机房」,B 机房叫「从机房」。两个机房部署在「同城」,物理距离比较近,而且两个机房用「专线」网络连接,虽然跨机房访问的延迟,比单个机房内要大一些,但整体的延迟还是可以接受的。业务改造完成后,B 机房可以慢慢接入流量,从 10%、30%、50% 逐渐覆盖到 100%,你可以持续观察 B 机房的业务是否存在问题,有问题及时修复,逐渐让 B 机房的工作能力,达到和 A 机房相同水平。

2. 跨城异地

(1)跨城异地带来的复杂性

跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。例如,将业务部署在北京和广州两个机房,而不是将业务部署在广州和深圳的两个机房。该种方式主要是为了解决大灾难性的问题。通常建议两个机房的距离要在 1000 公里以上,这样才能应对城市级别的灾难。

跨城异地虽然能够有效应对极端灾难事件,但“距离较远”这点并不只是一个距离数字上的变化,而是量变引起了质变,导致了跨城异地的架构复杂度大大上升。距离增加带来的最主要问题是两个机房的网络传输速度会降低,这不是以人的意志为转移的,而是物理定律决定的,即光速真空传播大约是每秒 30 万千米,在光纤中传输的速度大约是每秒 20 万千米,再加上传输中的各种网络设备的处理,实际还远远达不到理论上的速度。

除了距离上的限制,中间传输各种不可控的因素也非常多。例如,挖掘机把光纤挖断、中美海底电缆被拖船扯断、骨干网故障等,这些线路很多是第三方维护,针对故障我们根本无能为力也无法预知。例如,广州机房到北京机房,正常情况下 RTT 大约是 50 毫秒左右,遇到网络波动之类的情况,RTT 可能飙升到 500 毫秒甚至 1 秒,更不用说经常发生的线路丢包问题,那延迟可能就是几秒几十秒了。

如果业务需要多次跨机房请求应用的话,延迟的问题会彻底放大;避免跨机房调用服务

(2)跨城异地复杂性解决方法

如何解决跨城异地肯定会导致数据不一致问题呢?重点还是在“数据”上,即根据数据的特性来做不同的架构。如果是强一致性要求的数据,例如银行存款余额、支付宝余额等,这类数据实际上是无法做到跨城异地多活的。

而对数据一致性要求不那么高,或者数据不怎么改变,或者即使数据丢失影响也不大的业务,跨城异地多活就能够派上用场了。例如,用户登录(数据不一致时用户重新登录即可)、新闻类网站(一天内的新闻数据变化较少)、微博类网站(丢失用户发布的微博或者评论影响不大),这些业务采用跨城异地多活,能够很好地应对极端灾难的场景。

(3)数据一致性解决方案——异步同步数据

  • 基于存储系统的主从复制,比如 MySQL 和 Redis。也就是在一个机房部署主库, 在异地机房部署从库,两者同步主从复制, 实现数据的同步。

  • 基于消息队列的方式。一个机房产生写入请求后,会写一条消息到消息队列, 另一个机房的应用消费这条消息后,再执行业务处理逻辑,写入到存储服务中。

建议采用两种同步相结合的方式,比如可以基于消息的方式,同步缓存的数据、 HBase 数据等。然后基于存储,主从复制同步 MySQL、Redis 等数据。无论是采取哪种方案,数据从一个机房,传输到另一个机房都会有延迟,所以需要尽量 保证用户在读取自己的数据时,读取数据主库所在的机房。为了达到这一点,需要对用户做分片,让一个用户每次的读写都尽量在同一个机房中。同时,在数据读取和服务调用时, 也要尽量调用本机房的服务。

(4)跨城异地不同模式

①两地三中心

两地是指 2 个城市,三中心是指有 3 个机房,其中 2 个机房在同一个城市,并且同时提供服务,第 3 个机房部署在异地,只做数据灾备。这种架构方案,通常用在银行、金融、政企相关的项目中。它的问题在于启用灾备机房需要时间,而且启用后的服务,不确定能否如期工作。

②异地双活

图片

因为只有两个机房都拥有「全量数据」,才能支持任意切换机房,持续提供服务。怎么实现这种「双主」架构呢?它们之间如何互相同步数据?

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

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

③异地多活

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

(5)如何实施异地双活

但这里会遇到一个问题,两个机房都可以写,操作的不是同一条数据那还好,如果修改的是同一条的数据,发生冲突怎么办?

  • 用户短时间内发了 2 个修改请求,都是修改同一条数据

  • 一个请求落在北京机房,修改 X = 1(还未同步到上海机房)

  • 另一个请求落在上海机房,修改 X = 2(还未同步到北京机房)

  • 两个机房以哪个为准?

也就是说,在很短的时间内,同一个用户修改同一条数据,两个机房无法确认谁先谁后,数据发生「冲突」。这是一个很严重的问题,系统发生故障并不可怕,可怕的是数据发生「错误」,因为修正数据的成本太高了,一定要避免这种情况的发生。

从源头避免数据冲突的思路是:在最上层接入流量时,就不要让冲突的情况发生。具体来讲就是,要在最上层就把用户「区分」开,部分用户请求固定打到北京机房,其它用户请求固定打到上海 机房,进入某个机房的用户请求,之后的所有业务操作,都在这一个机房内完成,从根源上避免「跨机房」。所以这时,需要在接入层之上,再部署一个「路由层」(通常部署在云服务器上),自己可以配置路由规则,把用户「分流」到不同的机房内。

图片

①按业务类型分片

这种方案是指,按应用的「业务类型」来划分。举例:假设我们一共有 4 个应用,北京和上海机房都部署这些应用。但应用 1、2 只在北京机房接入流量,在上海机房只是热备。应用 3、4 只在上海机房接入流量,在北京机房是热备。这样一来,应用 1、2 的所有业务请求,只读写北京机房存储,应用 3、4 的所有请求,只会读写上海机房存储。

这里按业务类型在不同机房接入流量,还需要考虑多个应用之间的依赖关系,要尽可能的把完成「相关」业务的应用部署在同一个机房,避免跨机房调用。例如,订单、支付服务有依赖关系,会产生互相调用,那这 2 个服务在 A 机房接入流量。社区、发帖服务有依赖关系,那这 2 个服务在 B 机房接入流量。

②直接哈希分片

这种方案就是,最上层的路由层,会根据用户 ID 计算「哈希」取模,然后从路由表中找到对应的机房,之后把请求转发到指定机房内。举例:一共 200 个用户,根据用户 ID 计算哈希值,然后根据路由规则,把用户 1 - 100 路由到北京机房,101 - 200 用户路由到上海机房,这样,就避免了同一个用户修改同一条数据的情况发生。

③按地理位置分片

这种方案,非常适合与地理位置密切相关的业务,例如打车、外卖服务就非常适合这种方案。拿外卖服务举例,你要点外卖肯定是「就近」点餐,整个业务范围相关的有商家、用户、骑手,它们都是在相同的地理位置内的。针对这种特征,就可以在最上层,按用户的「地理位置」来做分片,分散到不同的机房。

举例:北京、河北地区的用户点餐,请求只会打到北京机房,而上海、浙江地区的用户,请求则只会打到上海机房。这样的分片规则,也能避免数据冲突。

分片的核心思路在于,让同一个用户的相关请求,只在一个机房内完成所有业务「闭环」,不再出现「跨机房」访问。

当然,最上层的路由层把用户分片后,理论来说同一个用户只会落在同一个机房内,但不排除程序 Bug 导致用户会在两个机房「漂移」。安全起见,每个机房在写存储时,还需要有一套机制,能够检测「数据归属」,应用层操作存储时,需要通过中间件来做「兜底」,避免不该写本机房的情况发生。(篇幅限制,这里不展开讲,理解思路即可)

这里还有一种情况,是无法做数据分片的:全局数据。例如系统配置、商品库存这类需要强一致的数据,这类服务依旧只能采用写主机房,读从机房的方案,不做双活。双活的重点,是要优先保证「核心」业务先实现双活,并不是「全部」业务实现双活。

③跨国异地

跨国异地指的是业务部署在不同国家的多个机房。相比跨城异地,跨国异地的距离就更远了,因此数据同步的延时会更长,正常情况下可能就有几秒钟了。这种程度的延迟已经无法满足异地多活标准的第一条:“正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务”。例如,假设有一个微博类网站,分别在中国的上海和美国的纽约都建了机房,用户 A 在上海机房发表了一篇微博,此时如果他的一个关注者 B 用户访问到美国的机房,很可能无法看到用户 A 刚刚发表的微博。虽然跨城异地也会有此类同步延时问题,但正常情况下几十毫秒的延时对用户来说基本无感知的;而延时达到几秒钟就感觉比较明显了。

因此,跨国异地的“多活”,和跨城异地的“多活”,实际的含义并不完全一致。跨国异地多 活的主要应用场景一般有这几种情况:

  • 为不同地区用户提供服务。例如,亚马逊中国是为中国用户服务的,而亚马逊美国是为美国用户服务的,亚马逊中国的用户如果访问美国亚马逊,是无法用亚马逊中国的账号登录美国亚马逊的。

  • 只读类业务做多活。例如,谷歌的搜索业务,由于用户搜索资料时,这些资料都已经存在于谷歌的搜索引擎上面,无论是访问英国谷歌,还是访问美国谷歌,搜索结果基本相同,并且对用户来说,也不需要搜索到最新的实时资料,跨国异地的几秒钟网络延迟,对搜索结果是没有什么影响的。

4.微博异地多活

由于几十毫秒的延时,跨机房服务调用性能很差,异地多活部署的主体服务必须要做数据的冗余存储,并辅以缓存等构成一套独立而相对完整的服务。数据同步有很多层面,包括消息层面、缓存层面、数据库层面,每一个层面都可以做数据同步。通过消息对缓存更新,加上缓存高可用架构,可以做到即便数据库同步有问题从用户体验看服务还是正常的。

这套方案中,每个机房的缓存是完全独立的,由每个机房的Processor(专门负责消息处理的程序,类Storm)根据收到的消息进行缓存更新。由于消息不会重复分发,而且信息完备,所以MytriggerQ方案存在的缓存更新脏数据问题就解决了。而当缓存不存在时,会穿透到MySQL从库,然后进行回种。可能出现的问题是,缓存穿透,但是MySQL从库如果此时出现延迟,这样就会把脏数据种到缓存中。我们的解决方案是做一个延时10分钟的消息队列,然后由一个处理程序来根据这个消息做数据的重新载入。一般从库延时时间不超过10分钟,而10分钟内的脏数据在微博的业务场景下也是可以接受的。

图片

由于微博对数据库不是强依赖,加上数据库双写的维护成本过大,我们选择的方案是数据库通过主从同步的方式进行。这套方案可能的缺点是如果主从同步慢,并且缓存穿透

数据同步问题解决之后,紧接着就要解决依赖服务部署的问题。由于微博平台对外提供的都是Restful风格的API接口,所以独立业务的接口可以直接通过专线引流回北京机房。但是对于微博Feed接口的依赖服务,直接引流回北京机房会将平均处理时间从百毫秒的量级直接升至几秒的量级,这对服务是无法接受的。所以,在2012年对微博Feed依赖的主要服务也做了异地多活部署,整体的处理时间终于降了下来。当然这不是最优的解决方案,但在当时微博业务体系还相对简单的情况下很好的解决了问题,确保了2012年5月的广州机房部署任务的达成。

而配套体系的问题,技术上不是很复杂,但是操作的时候缺很容易出问题。比如,微博刚开始做异地多活部署时,测试同学在上线时对广州机房做预览测试,曾经导致过一些线上问题。配套体系需要覆盖整个业务研发周期,包括方案设计阶段的是否要做多机房部署、部署阶段的数据同步、发布预览、发布工具支持、监控覆盖支持、降级工具支持、流量迁移工具支持等方方面面,并需开发、测试、运维都参与进来,将关键点纳入到流程当中。

关于为应对故障而进行数据冗余问题,微博核心池容量冗余分两个层面来做,前端Web层冗余同用户规模成正比,并预留日常峰值50%左右的冗余度,而后端缓存等资源由于相对成本较低,每个机房均按照整体两倍的规模进行冗余。这样如果某一个机房不可用,首先后端的资源是足够的。接着首先会只将核心接口进行迁移,这个操作分钟级即可完成,同时由于冗余是按照整体的50%,所以即使所有的核心接口流量全部迁移过来也能支撑住。接下来,我们将会把其他服务池的前端机也改为部署核心池前端机,这样在一小时内即可实现整体流量的承接。同时,如果故障机房是负责数据落地的机房,DBA会将从库升为主库,运维调整队列机开关配置,承接数据落地功能。而在整个过程中,由于核心缓存可以脱离数据库支撑一个小时左右,所以服务整体会保持平稳。

三、跨城异地多活架构设计四大技巧

1.保证核心业务的异地多活

“异地多活”是为了保证业务的高可用,但很多架构师在考虑这个“业务”时,会不自觉地陷入一个思维误区:我要保证所有业务都能“异地多活”!

假设我们需要做一个“用户子系统”,这个子系统负责“注册”“登录”“用户信息”三个业务。为了支持海量用户,我们设计了一个“用户分区”的架构,即正常情况下用户属于某个主分区,每个分区都有其他数据的备份,用户用邮箱或者手机号注册,路由层拿到邮箱或者手机号后,通过 Hash 计算属于哪个中心,然后请求对应的业务中心。基本的架构如下:

图片

这样一个系统,如果 3 个业务要同时实现异地多活,会发现这些难以解决的问题:

(1)注册问题

A 中心注册了用户,数据还未同步到 B 中心,此时 A 中心宕机,为了支持注册业务多活,可以挑选 B 中心让用户去重新注册。看起来很容易就支持多活了,但仔细思考一下会发现这样做会有问题:一个手机号只能注册一个账号,A 中心的数据没有同步过来,B 中心无法判断这个手机号是否重复,如果 B 中心让用户注册,后来 A 中心恢复了,发现数据有冲突,怎么解决?实际上是无法解决的,因为同一个手机号注册的账号不能以后一次注册为准;而如果 B 中心不支持本来属于 A 中心的业务进行注册,注册业务的多活又成了空谈。

如果我们修改业务规则,允许一个手机号注册多个账号不就可以了吗?这样做是不可行的,类似一个手机号只能注册一个账号这种规则,是核心业务规则,修改核心业务规则的代价非常大,几乎所有的业务都要重新设计,为了架构设计去改变业务规则(而且是这么核心的业务规则)是得不偿失的。

(2)用户信息问题

用户信息的修改和注册有类似的问题,即 A、B 两个中心在异常的情况下都修改了用户信息,如何处理冲突?

由于用户信息并没有账号那么关键,一种简单的处理方式是按照时间合并,即最后修改的生效。业务逻辑上没问题,但实际操作也有一个很关键的“坑”:怎么保证多个中心所有机器时间绝对一致?在异地多中心的网络下,这个是无法保证的,即使有时间同步也无法完全保证,只要两个中心的时间误差超过 1 秒,数据就可能出现混乱,即先修改的反而生效。还有一种方式是生成全局唯一递增 ID,这个方案的成本很高,因为这个全局唯一递增 ID 的系统本身又要考虑异地多活,同样涉及数据一致性和冲突的问题。

所以“注册”“登录”“用户信息”全部都要支持异地多活,实际上是挺难的。这种情况下应该优先实现核心业务的异地多活架构。对于这个模拟案例来说,“登录”才是最核心的业务,“注册”和“用户信息”虽然也是主要业务,但并不一定要实现异地多活,主要原因在于业务影响不同。对于一个日活 1000 万的业务来说,每天注册用户可能是几万,修改用户信息的可能还不到 1 万,但登录用户是 1000 万,很明显我们应该保证登录的异地多活。

对于新用户来说,注册不了的影响并不明显,因为他还没有真正开始使用业务。用户信息修改也类似,暂时修改不了用户信息,对于其业务不会有很大影响。而如果有几百万用户登录不了,就相当于几百万用户无法使用业务,对业务的影响就非常大了。而登录实现“异地多活”恰恰是最简单的,因为每个中心都有所有用户的账号和密码信息,用户在哪个中心都可以登录。用户在 A 中心登录,A 中心宕机后,用户到 B 中心重新登录即可。

2.保证核心数据最终一致性

异地多活本质上是通过异地的数据冗余,来保证在极端异常的情况下业务也能够正常提供给用户,因此数据同步是异地多活架构设计的核心。数据冗余是要将数据从 A 地同步到 B 地,从业务的角度来看是越快越好,最好和本地机房一样的速度最好。但让人头疼的问题正在这里:异地多活理论上就不可能很快,因为这是物理定律决定的。因此异地多活架构面临一个无法彻底解决的矛盾:业务上要求数据快速同步,物理上正好做不到数据快速同步,因此所有数据都实时同步,实际上是一个无法达到的目标。既然是无法彻底解决的矛盾,那就只能想办法尽量减少影响。有几种方法可以参考:

  • 尽量减少异地多活机房的距离,搭建高速网络。但搭建跨城异地的高速网络成本远远超过同城异区的高速网络,成本巨大,一般只有巨头公司才能承担。

  • 尽量减少数据同步,只同步核心业务相关的数据。简单来说就是不重要的数据不同步,同步后没用的数据不同步,只同步核心业务相关的数据。

以前面的“用户子系统”为例,用户登录所产生的 token 或者 session 信息,数据量很大,但其实并不需要同步到其他业务中心,因为这些数据丢失后重新登录就可以再次获取了。

保证最终一致性,不保证实时一致性,即业务不依赖数据同步的实时性,只要数据最终能一致即可。例如,A 机房注册了一个用户,业务上不要求能够在 50 毫秒内就同步到所有机房,正常情况下要求 5 分钟同步到所有机房即可,异常情况下甚至可以允许 1 小时或者 1 天后能够一致。

最终一致性在具体实现时,还需要根据不同的数据特征,进行差异化的处理,以满足业务需要。例如,对“账号”信息来说,如果在 A 机房新注册的用户 5 分钟内正好跑到 B 机房了,此时 B 机房还没有这个用户的信息,为了保证业务的正确,B 机房就需要根据路由规则到 A 机房请求数据。

3.采用多种手段同步数据

数据同步是异地多活架构设计的核心,幸运的是基本上存储系统本身都会有同步的功能。例如,MySQL 的主备复制、Redis 的 Cluster 功能、Elasticsearch 的集群功能。这些系统本身的同步功能已经比较强大,能够直接拿来就用,但是并不是只能使用存储系统完成同步功能,

因为虽然绝大部分场景下,存储系统本身的同步功能基本上也够用了,但在某些比较极端的情况下,存储系统本身的同步功能可能难以满足业务需求。以 MySQL 为例,MySQL 5.1 版本的复制是单线程的复制,在网络抖动或者大量数据同步时,经常发生延迟较长的问题,短则延迟十几秒,长则可能达到十几分钟。而且即使我们通过监控的手段知道了 MySQL 同步时延较长,也难以采取什么措施,只能干等。

Redis 又是另外一个问题,Redis 3.0 之前没有 Cluster 功能,只有主从复制功能,而为了设计上的简单,Redis 2.8 之前的版本,主从复制有一个比较大的隐患:从机宕机或者和主机断开连接都需要重新连接主机,重新连接主机都会触发全量的主从复制。这时主机会生成内存快照,主机依然可以对外提供服务,但是作为读的从机,就无法提供对外服务了,如果数据量大,恢复的时间会相当长。

综合上面的案例可以看出,存储系统本身自带的同步功能,在某些场景下是无法满足业务需要的。尤其是异地多机房这种部署,各种各样的异常情况都可能出现,当我们只考虑存储系统本身的同步功能时,就会发现无法做到真正的异地多活。

解决的方案就是拓开思路,避免只使用存储系统的同步功能,可以将多种手段配合存储系统的同步来使用,甚至可以不采用存储系统的同步方案,改用自己的同步方案。还是以前面的“用户子系统”为例,我们可以采用如下几种方式同步数据:

(1)消息队列方式

对于账号数据,由于账号只会创建,不会修改和删除(假设我们不提供删除功能),我们可以将账号数据通过消息队列同步到其他业务中心。

(2)二次读取方式

某些情况下可能出现消息队列同步也延迟了,用户在 A 中心注册,然后访问 B 中心的业务,此时 B 中心本地拿不到用户的账号数据。为了解决这个问题,B 中心在读取本地数据失败时,可以根据路由规则,再去 A 中心访问一次(这就是所谓的二次读取,第一次读取本地,本地失败后第二次读取对端),这样就能够解决异常情况下同步延迟的问题。

(3)存储系统同步方式

对于密码数据,由于用户改密码频率较低,而且用户不可能在 1 秒内连续改多次密码,所以通过数据库的同步机制将数据复制到其他业务中心即可,用户信息数据和密码类似。

(4)回源读取方式

对于登录的 session 数据,由于数据量很大,我们可以不同步数据;但当用户在 A 中心登录后,然后又在 B 中心登录,B 中心拿到用户上传的 session id 后,根据路由判断 session 属于 A 中心,直接去 A 中心请求 session 数据即可;反之亦然,A 中心也可以到 B 中心去获取 session 数据。

(5)重新生成数据方式

对于“回源读取”场景,如果异常情况下,A 中心宕机了,B 中心请求 session 数据失败,此时就只能登录失败,让用户重新在 B 中心登录,生成新的 session 数据。

综合上述的各种措施,最后“用户子系统”同步方式整体如下:

图片

4.只保证绝大部分用户的异地多活

异地多活也无法保证 100% 的业务可用,这是由物理规律决定的,光速和网络的传播速度、硬盘的读写速度、极端异常情况的不可控等,都是无法 100% 解决的。对于某些实时强一致性的业务,实际上受影响的用户会更多,甚至可能达到 1/3 的用户。以银行转账这个业务为例,假设小明在北京 XX 银行开了账号,如果小明要转账,一定要北京的银行业务中心才可用,否则就不允许小明自己转账。如果不这样的话,假设在北京和上海两个业务中心实现了实时转账的异地多活,某些异常情况下就可能出现小明只有 1 万元存款,他在北京转给了张三 1 万元,然后又到上海转给了李四 1 万元,两次转账都成功了。这种漏洞如果被人利用,后果不堪设想。

当然,针对银行转账这个业务,虽然无法做到“实时转账”的异地多活,但可以通过特殊的业务手段让转账业务也能实现异地多活。例如,转账业务除了“实时转账”外,还提供“转账申请”业务,即小明在上海业务中心提交转账请求,但上海的业务中心并不立即转账,而是记录这个转账请求,然后后台异步发起真正的转账操作,如果此时北京业务中心不可用,转账请求就可以继续等待重试;假设等待 2 个小时后北京业务中心恢复了,此时上海业务中心去请求转账,发现余额不够,这个转账请求就失败了。小明再登录上来就会看到转账申请失败,原因是“余额不足”。

不过需要注意的是“转账申请”的这种方式虽然有助于实现异地多活,但其实还是牺牲了用户体验的,对于小明来说,本来一次操作的事情,需要分为两次:一次提交转账申请,另外一次是要确认是否转账成功。对于这种影响用户体验的结果可以采用以下方式进行弥补:

  • 挂公告。说明现在有问题和基本的问题原因,如果不明确原因或者不方便说出原因,可以发布“技术哥哥正在紧急处理”这类比较轻松和有趣的公告。

  • 事后对用户进行补偿。例如,送一些业务上可用的代金券、小礼包等,减少用户的抱怨。

  • 补充体验。对于为了做异地多活而带来的体验损失,可以想一些方法减少或者规避。以“转账申请”为例,为了让用户不用确认转账申请是否成功,我们可以在转账成功或者失败后直接给用户发个短信,告诉他转账结果,这样用户就不用时不时地登录系统来确认转账是否成功了。

五、容灾

1.容灾简介

(1)什么是容灾

容灾系统是指建立两套或多套功能相同的IT系统,互相之间可以进行健康状态监视和功能切换。当一处系统因意外停止工作时,整个系统可以切换到另一处系统,使得系统功能可以继续工作。

    容灾即使是系统的高可用性技术的一个组成部分,荣在系统更加强调处理外界环境对系统的影响,特别是灾难性事件对整个IT节点的影响,提供节点级别的系统恢复功能

(2)容灾分类

    从其对系统的保护程度来分,可以讲容灾系统分为:数据容灾和应用容灾。

  • 数据容灾:指建立一个异地的数据系统,该系统可以是本地关键应用数据的一个备份或实时复制。

  • 应用容灾:指在数据容灾的基础上,在异地建立一套完整的与本地应用生产系统相当的备份应用系统(可互为备份)。在灾难情况下,远程系统迅速接管业务运行。

    数据容灾是对灾难抵御的保障,而应用容灾则是容灾系统建设的目标。

①数据容灾

数据容灾,就是至少在异地保存一份可用的关键业务数据,该数据可以是与本地生产数据的完全实时复制,也可以比本地数据稍微落后,但一定是可用的。

    采用的主要技术是 数据备份和 数据复制技术。其中数据复制技术,按照实现的技术方式来说可分为 同步传输和 异步传输。

②应用容灾

所谓应用容灾,是在数据容灾的基础上,在异地建立一套完整的与本地生产系统相当的备份应用系统。建立这样一套系统相对比较复杂,不仅需要一份可用的数据复制,还要有包括网络、主机、应用、甚至IP等资源,以及各资源之间良好协调。

    在远程的容灾系统中,要实现完整的应用容灾,既要包含本地系统的安全机制、远程的数据复制机制,还应具有广域网范围的 远程故障切换能力和 故障诊断能力。

    也就是说,一旦故障发生,系统要有强大的故障诊断和切换策略制定机制,确保快速的反应和迅速的业务接管。实际上,广域网范围的高可用能力和本地系统的高可用能力应形成一个整体,实现多级的故障切换和恢复机制,确保系统在各个范围的可靠和安全。

2.重要指标

  • RTO: 恢复时间目标(Recovery Time Objective,简称RTO)是指灾难发生后,从IT系统宕机导致业务停顿时刻开始,到IT系统恢复至业务恢复运营为止,此两点之间的时间段称为RTO。

  • RPO:恢复点目标(Recovery Point Objective,简称RPO)是对系统和应用数据而言,指的是恢复业务系统所能容忍的数据丢失量。

3.灾备方法论

图片

(1)风险分析

  • 分析可能对用户业务系统和IT系统的安全性造成威胁的各种风险因素,并提出相应的对策和改进方案。

  • 定义出对于风险的预防措施

(2)业务影响分析

    业务影响分析(Business Impact Analysis,简称BIA)。

    指收集、分析、汇总以及排序当信息系统一旦遭遇灾害对各项重要关键性业务的影响程度,并依据优先级提出恢复策略建议。

  • 确定用户的关键业务流程

  • 定义各关键业务中容许中断的最长时间

  • 确认各关键业务数据丢失的可容许程度

(3)可恢复性评估

    从IT架构、平台、技术、基础设施、组织结构、恢复流程等各层面来评估用户IT目前的恢复能力。

    评估IT作业目前是否能够恢复、需要多少时间恢复、以及可能的数据丢失数量。

(4)指定恢复策略

  • 依据前述各项分析,配合目前技术,提出适当的灾难恢复策略。

  • 召开研讨会确认恢复策略。

(5)容灾方案设计

  • 根据恢复策略来设计最适合的容灾技术方案。

  • 各种容灾解决方案的比较。

  • 在设计容灾方案时,应综合考虑基础设施、硬件平台、软件技术、网络配置、IT组织、技术恢复流程等方面。

(6)灾难恢复原设计

  • 定义可被接受的灾难恢复的规范

  • 定义必须遵循的恢复程序,包括IT系统和相关设施

  • 设计相应的容灾组织结构和人员职责

(7)容灾演练和维护

  • 通过容灾演练、测试确保灾难恢预案的有效性。

  • 灾难恢复预案的维护包括:

  • 日常计划维护

  • 根据容灾演练结果的维护

  • 由于各项变更而产生的维护

4.灾难恢复规范

图片

(1)容灾管理要素

图片

  • 灾难恢复预案

  • 运行维护支持

  • 技术支持

  • 备用基础设施

  • 备用网络系统

  • 备用数据处理系统

  • 数据备份系统

(2)容灾建设技术

  • 数据库复制技术

  • 主机卷复制技术

  • 磁盘到磁盘的复制技术

  • 虚拟存储层的复制技术

(3)容灾建设规划要点

①灾备实现方法论

  • 分析评估:包括风险的分析、业务影响的分析和当前环境的分析。

  • 设计实施:包括容错策略的制定、容灾方案的规划和实施。

  • 维护管理:包括灾难恢复原的设计和管理。如何实现灾备/备份和其他资源的统一管理。

②灾备建设目标和范围

  • 核心业务系统

  • 周边业务系统

  • 应用级容灾还是数据级容灾

  • RPO和RTO指标

③容灾技术路线选型依据

  • 复制数据类型要求:数据库、文件系统

  • 链路带宽

  • 是否要求主机、存储异构

  • 传数距离

  • 是否要求双活

  • RTO、RPO要求

六、数据库高可用

1.单体数据库高可用

(1)异地容灾

图片

整个方案涉及同城和异地两个机房,都部署了同样的应用服务器和数据库,其中应用服务 器都处于运行状态可以处理请求,也就是应用多活。只有同城机房的数据库处于运行状 态,异地机房数据库并不启动,不过会通过底层存储设备向异地机房同步数据。然后,所 有应用都链接到同城机房的数据库。

图片

这样当同城机房整体不可用时,异地机房的数据库会被拉起并加载数据,同时异地机房的 应用切换到异地机房的数据库。显然,这个多活只是应用服务器的多活,两地的数据库并不同时提供服务。这种模式下, 异地应用虽然靠近用户,但仍然要访问远端的数据库,对延迟的改善并不大。在系统正常 运行情况下,异地数据库并没有实际产出,造成了资源的浪费。

(2)异地读写分离

在异地读写分离模式下,异地数据库和主机房数据库同时对外提供服务,但服务类型限制为只读服务,但只读服务的数据一致性是不保证的。

图片

读写分离模式下,异地数据库也投入了使用,不再是闲置的资源。但是很多场景下,只读 服务在业务服务中的占比还是比较低的,再加上不能保证数据的强一致性,适用范围又被 进一步缩小。

(3)双向同步

双向同步模式下,同城和异地的数据库同时提供服务,并且是读写服务均开放。但有一个 重要的约束,就是两地读写的对象必须是不同的数据,区分方式可以是不同的表或者表内 的不同记录,这个约束是为了保证两地数据操作不会冲突。因为不需要处理跨区域的事务 冲突,所以两地数据库之间就可以采用异步同步的方式。

图片

这个模式下,两地处理的数据是泾渭分明的,所以实质上是两个独立的应用实例,或者可 以说是两个独立的单元。而两个单元之间又相互备 份数据,有了这些数据可以容灾,也可以开放只读服务。当然,这个只读服务同样是不保 证数据一致性的。可以说,双向同步是单元化架构和异地读写分离的混合,异地机房的资源被充分使用了。但双向同步没有解决一个根本问题,就是两地仍然不能处理同样的数据,对于一个完整的 系统来说,这还是有很大局限性的。

2.分布式数据库

分布式数据库的数据组织单位是更细粒度的分片,又有了 Raft 协议的加持,所以就有了更 加灵活的模式。

(1)机房级容灾(两地三中心五副本)

比较典型的分布式数据库部署模式是两地三中心五副本。这种模式下,每个分片都有 5 个 副本,在同城的双机房各部署两个副本,异地机房部署一个副本。

图片

  • 异地备份:保留了“异地容灾”模式下的数据同步功能,但因为同样要保证低延迟,所以也做不到 RPO(Recovery Point Objective, 恢复点目标)为零。

  • 容灾能力:如果同城机房有一个不可用或者是同城机房间的网络出现故障,异地机房节点的投票就会 发挥作用,依然可以和同城可用的那个机房共同达成多数投票,那么数据库的服务就仍然 可以正常运行,当然这时提交过程必须要异地通讯,所以延迟会受到一定程度影响。

  • 同城访问低延迟:由于 Raft 或 Paxos 都是多数派协议,那么任何写操作时,同城的四个副本就能够超过半 数完成提交,这样就不会因为与异地机房通讯时间长而推高数据库的操作延迟。

(2)城市级容灾(三地五副本)

两地三中心虽然可以容灾,但对于异地机房来说 RPO 不为零,在更加苛刻的场景下,仍然 受到挑战。这也就催生了三地五副本模式,来实现 RPO 为零的城市级容灾。

三地五副本模式是两地三中心模式的升级版。两个同城机房变成两个相临城市的机房,这样总共是在三个城市部署。这种模式在容灾能力和延迟之间做了权衡,牺牲一点延迟,换来城市级别的容灾。比如, 在北京和天津建立两座机房,两个城市距离不算太远,延迟在 10 毫秒以内,还是可以接受 的。不过,距离较近的城市对区域性灾难的抵御能力不强,还不是真正意义上的异地。

顺着这思路,还有更大规模的三地五中心七副本。但无论如何,只要不放弃低延迟,真正 异地机房就无法做到 RPO 为零。无论是两地三中心五副本还是三地五副本,它们更像是单体数据库异地容灾的加强版。因 为,其中的异地机房始终是一个替补的角色,而那些异地的应用服务器也依然花费很多时 间访问远端的数据库。

(3)架构问题

为有些分布式数据库会有一个限制条件,就是所有的 Leader 节点必须 固定在同城主机房,而这就导致了资源使用率大幅下降。TiDB 和 OceanBase 都是这种情 况。

Raft 协议下,所有读写都是发送到 Leader 节点,Follower 节点是没有太大负载的。Raft 协议的复制单位是分片级的,所以理论上一个节点可以既是一些分片的 Leader,又是另一 些分片的 Follower。也就是说,通过 Leader 和 Follower 混合部署可以充分利用硬件资 源。但是如果主副本只能存在同一个机房,那就意味着另外三个机房的节点,也就是有整个集 群五分之三的资源,在绝大多数时候都处于低负载状态。这显然是不经济的。单时间源的授时服务器不能距离 Leader 太远,否则会增加通讯延迟,性能就受到很大影响,极端 情况下还会出现异常。

  • 26
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值