四步构建异地多活

来自:技术琐话(微信号:TheoryPractice),作者:李运华



第一步:业务分级


按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。


常见的分级标准有如下几种。


  • 访问量大的业务

以用户管理系统为例,业务包括登录、注册、用户信息管理,其中登录的访问量肯定是最大的。


  • 核心业务

QQ为例,QQ的主场景是聊天,QQ空间虽然也是重要业务,但和聊天相比,重要性就会低一些,如果要从聊天和QQ空间两个业务里面挑选一个做异地多活,那明显聊天要更重要(当然,腾讯此类公司,应该是两个都实现了异地多活的)。


  • 产生大量收入的业务

同样以QQ为例,聊天可能很难为腾讯带来收益,因为聊天没法插入广告,而QQ空间反而可能带来更多收益,因为QQ空间可以插入很多广告,因此如果从收入的角度来看,QQ空间做异地多活的优先级反而高于QQ聊天了。


以我们的用户管理系统为例,“登录”业务符合“访问量大的业务”和“核心业务”这两条标准,因此我们将登录业务作为核心业务。



第二步:数据分类


挑选出核心业务后,需要对核心业务相关的数据进行进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。


常见的数据特征分析维度如下。



  • 数据量

这里的数据量包括总的数据量和新增、修改、删除的量。对异地多活架构来说,新增、修改、删除的数据就是可能要同步的数据,数据量越大,同步延迟的概率越高,同步方案需要考虑相应的解决方案。


  • 唯一性

唯一性指数据是否要求多个异地机房产生的同类数据必须保证唯一。例如,用户ID,如果两个机房的两个不同用户注册后生成了一样的用户ID,这样业务上就出错了。

数据的唯一性影响业务的多活设计,如果数据不需要唯一,那就说明两个地方都产生同类数据是可能的;如果数据要求必须唯一,要么只能一个中心点产生数据,要么需要设计一个数据唯一生成的算法。


  • 实时性

实时性指如果在A机房修改了数据,要求多长时间必须同步到B机房,实时性要求越高,对同步的要求越高,方案越复杂。


  • 可丢失性

可丢失性指数据是否可以丢失。例如,写入A机房的数据还没有同步到B机房,此时A机房机器宕机会导致数据丢失,那这部分丢失的数据是否对业务会产生重大影响。

例如,登录过程中产生的session数据就是可丢失的,因为用户只要重新登录就可以生成新的session;而用户ID数据是不可丢失的,丢失后用户就会失去所有和用户ID相关的数据,例如,用户的好友、用户的钱等。


  • 可恢复性

可恢复性指数据丢失后,是否可以通过某种手段进行恢复,如果数据可以恢复,至少说明对业务的影响不会那么大,这样可以相应地降低异地多活架构设计的复杂度。

例如,用户的微博丢失后,用户重新发一篇一模一样的微博,这个就是可恢复的;或者用户密码丢失,用户可以通过找回密码来重新设置一个新密码,这也算是可以恢复的;而用户账号如果丢失,用户无法登录系统,系统也无法通过其他途径来恢复这个账号,这就是不可恢复的数据。


我们同样以用户管理系统的登录业务为例,简单分析如下表所示。

 

数  据

数 据 量

唯 一 性

实 时 性

可丢失性

可恢复性

用户ID

每天新增1万注册用户

全局唯一

5s内同步

不可丢失

不可恢复

用户密码

每天1千用户修改密码

用户唯一

5s内同步

可丢失

可重置密码恢复

登录session

每天1000

全局唯一

无须同步

可丢失

可重复生成



第三步:数据同步


确定数据的特点后,我们可以根据不同的数据设计不同的同步方案。常见的数据同步方案如下。




  • 存储系统同步

这是最常用也是最简单的同步方式。例如,使用MySQL的数据主从数据同步、主主数据同步。

这类数据同步的优点是使用简单,因为几乎主流的存储系统都会有自己的同步方案;缺点是这类同步方案都是通用的,无法针对业务数据特点做定制化的控制。例如,无论需要同步的数据量有多大,MySQL都只有一个同步通道。因为要保证事务性,一旦数据量比较大,或者网络有延迟,则同步延迟就会比较严重。


  • 消息队列同步

采用独立消息队列进行数据同步,常见的消息队列有KafkaActiveMQRocketMQ等。


消息队列同步适合无事务性或无时序性要求的数据。例如,用户账号,两个用户先后注册了账号AB,如果同步时先把B同步到异地机房,再同步A到异地机房,业务上是没有问题的。而如果是用户密码,用户先改了密码为m,然后改了密码为n,同步时必须先保证同步m到异地机房,再同步n到异地机房,如果反过来,同步后用户的密码就不对了。因此,对于新注册的用户账号,我们可以采用消息队列同步了;而对于用户密码,就不能采用消息队列同步了。


  • 重复生成

数据不同步到异地机房,每个机房都可以生成数据,这个方案适合于可以重复生成的数据。例如,登录产生的cookiesession数据及缓存数据等。


我们同样以用户管理系统的登录业务为例,针对不同的数据特点设计不同的同步方案,如下表所示。

 

数  据

数据量

唯一性

实时性

可 丢 失 性

可 恢 复 性

同 步 方 案

用户ID

每天新增1万注册用户

全局唯一

5s内同步

不可丢失

不可恢复

消息队列同步

用户密码

每天1千用户修改密码

用户唯一

5s内同步

可丢失

可重置密码恢复

MySQL同步

登录session

每天1000

全局唯一

无须同步

可丢失

可重复生成

重复生成

 


第四步:异常处理


无论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的。例如,同步延迟、数据丢失、数据不一致等。异常处理就是假设在出现这些问题时,系统将采取什么措施来应对。异常处理主要有以下几个目的:


  • 问题发生时,避免少量数据异常导致整体业务不可用。

  • 问题恢复后,将异常的数据进行修正。

  • 对用户进行安抚,弥补用户损失。


常见的异常处理措施有如下几类。


(1)多通道同步。

多通道同步的含义是采取多种方式来进行数据同步,其中某条通道故障的情况下,系统可以通过其他方式来进行同步,这种方式可以应对同步通道处故障的情况。


我们以用户管理系统中的用户账号数据为例,我们的设计方案一开始挑选了消息队列的方式进行同步,考虑异常情况下,消息队列同步通道可能中断,也可能延迟很严重;为了保证新注册账号能够快速同步到异地机房,我们再增加一种MySQL同步这种方式作为备份。这样针对用户账号数据同步,系统就有两种同步方式:MySQL主从同步和消息队列同步。除非两个通道同时故障,否则用户账号数据在其中一个通道异常的情况下,能够通过另外一个通道继续同步到异地机房,如下图所示。



多通道同步设计的方案关键点有如下几个:


  • 一般情况下,采取两通道即可,采取更多通道理论上能够降低风险,但付出的成本也会增加很多。

  • 数据库同步通道和消息队列同步通道不能采用相同的网络连接,否则一旦网络故障,两个通道都同时故障;可以一个走公网连接,一个走内网连接。

  • 需要数据是可以重复覆盖的,即无论哪个通道先到哪个通道后到,最终结果是一样的。例如,新建账号数据就符合这个标准,而密码数据则不符合这个标准。


(2)同步和访问结合。

这里的访问指异地机房通过系统的接口来进行数据访问。例如业务部署在异地两个机房ABB机房的业务系统通过接口来访问A机房的系统获取账号信息,如下图所示。


同步和访问结合方案的设计关键点如下:

  • 接口访问通道和数据库同步通道不能采用相同的网络连接,不能让数据库同步和接口访问都走同一条网络通道,可以采用接口访问走公网连接,数据库同步走内网连接这种方式。

  • 数据有路由规则,可以根据数据来推断应该访问哪个机房的接口来读取数据。例如,有3个机房ABCB机房拿到一个不属于B机房的数据后,需要根据路由规则判断是访问A机房接口,还是访问C机房接口。

  • 由于有同步通道,优先读取本地数据,本地数据无法读取到再通过接口去访问,这样可以大大降低跨机房的异地接口访问数量,适合于实时性要求非常高的数据。


(3)日志记录。

日志记录主要用于故障恢复后对数据进行恢复,通过在每个关键操作前后都记录相关日志,然后将日志保存在一个独立的地方,当故障恢复后,拿出日志跟数据进行对比,对数据进行修复。


为了应对不同级别的故障,日志保存的要求也不一样,常见的日志保存方式有如下几种:

  • 服务器上保存日志,数据库中保存数据,这种方式可以应对单台数据库服务器故障或宕机的情况。

  • 本地独立系统保存日志,这种方式可以应对某业务服务器和数据库同时宕机的情况。例如,服务器和数据库部署在同一个机架,或者同一个电源线路上,就会出现服务器和数据库同时宕机的情况。

  • 日志异地保存,这种方式可以应对机房宕机的情况。


以上不同方式,应对的故障越严重,方案本身的复杂度和成本就会越高,实际选择时需要综合考虑成本和收益情况。


(4)用户补偿。

无论采用什么样的异常处理措施,都只能最大限度地降低受到影响的范围和程度,无法完全做到没有任何影响。例如,双同步通道有可能同时出现故障,日志记录方案本身日志也可能丢失。因此,无论多么完美的方案,故障的场景下总是可能有一小部分用户业务上出问题,系统无法弥补这部分用户的损失。但我们可以采用人工的方式对用户进行补偿,弥补用户损失,培养用户的忠诚度。简单来说,系统的方案是为了保证99.99%的用户在故障的场景下业务不受影响,人工的补偿是为了弥补0.01%的用户的损失。


常见的补偿措施有送用户代金券、礼包、礼品、红包等,有时为了赢得用户口碑,付出的成本可能还会比较大,但综合最终的收益来看还是很值得的。例如,暴雪《炉石传说》2017年回档故障,暴雪给每个用户大约价值人民币200元的补偿,结果玩家都求暴雪再来一次回档,形象地说明了玩家对暴雪补偿的充分认可。

 

只要在2017年1月18日18点之前登录过国服《炉石传说》的玩家,均可获得与25卡牌包等值的补偿,具体如下:

Ÿ   1000游戏金币

Ÿ  15个卡牌包:经典卡牌包x5、上古之神的低语卡牌包x5、龙争虎斗加基森卡牌包x5

我们将在明天正式启动上述补偿方案,卡牌包和金币将陆续发送到玩家的游戏账户中。



接口级的故障应对方案


异地多活架构主要应对系统级的故障。例如,机器宕机、机房故障、网络故障等问题。这些系统级的故障虽然影响很大,但发生概率较小。实际业务运行过程中,还有另外一种故障影响可能没有系统级那么大,但发生的概率较高,这就是接口级的故障。


接口级故障的典型表现就是系统并没有宕机,网络也没有中断,但业务却出现问题了。例如,业务响应缓慢,大量访问超时,大量访问出现异常(给用户弹出提示“无法连接数据库”),这类问题的主要原因在于系统压力太大,负载太高,导致无法快速处理业务请求,由此引发更多的后续问题。例如,最常见数据库慢查询将数据库的服务器资源耗尽,导致读写超时,业务读写数据库时要么无法连接数据库,要么超时,最终用户看到的现象就是访问很慢,一会访问抛出异常,一会访问又是正常结果。


导致接口级故障的原因一般有如下几种:


  • 内部原因

程序bug导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存,等等。

  • 外部原因

黑客攻击、促销或抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢,等等。


解决接口级故障的核心思想和异地多活基本类似:优先保证核心业务,优先保证绝大部分用户。接下来我们看看具体的措施。


降级

降级指系统将某些业务或接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。例如,论坛可以降级为只能看帖子,不能发帖子;也可以降级为只能看帖子和评论,不能发评论;而App的日志上传接口,可以完全停掉一段时间,这段时间内App都不能上传日志。


降级的核心思想就是丢车保帅,优先保证核心业务。例如,对于论坛来说,90%的流量是看帖子,那我们就优先保证看帖的功能;对于一个App来说,日志上传接口只是一个辅助的功能,故障时完全可以停掉。


常见的实现降级的方式有如下几种。


  • 系统后门降级

简单来说,就是系统预留了后门用于降级操作。例如,系统提供一个降级URL,当访问这个URL时,就相当于执行降级指令,具体的降级指令通过URL的参数传入即可。这种方案有一定的安全隐患,所以也会在URL中加入密码这类安全措施。


系统后门降级的方式实现成本低,但主要缺点是如果服务器数量多,需要一台一台去操作,效率比较低,这在故障处理争分夺秒的场景下是比较浪费的。


  • 独立降级系统

为了解决系统后门降级方式的缺点,我们将降级操作独立到一个单独的系统中,可以实现复杂的权限管理、批量操作等功能。其基本架构如下图所示。



熔断

熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看好像都有禁止某个功能的意思,但内在含义是不同的,原因在于降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。


假设一个这样的场景:A服务的X功能依赖B服务的某个接口,当B服务的接口响应很慢的时候,A服务的X功能响应肯定也会被拖慢,进一步导致A服务的线程都被卡在X功能处理上,此时A服务的其他功能都会被卡住或响应非常慢。这时就需要熔断机制了,即A服务不再请求B服务的这个接口,A服务内部只要发现请求B服务的这个接口就立即返回错误,从而避免A服务整个被拖慢甚至拖死。


熔断机制实现的关键是需要有一个统一的API调用层,由API调用层来进行采样或统计,如果接口调用散落在代码各处就没法进行统一处理了。


熔断机制实现的另外一个关键是阈值的设计,例如,1分钟内30%的请求响应时间超过1秒就熔断,这个策略中的“1分钟”“30%”“1秒”都对最终的熔断效果有影响。实践中一般先根据分析确定阈值,然后上线观察效果,最后进行调优。


限流

降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。


虽然“丢弃”这个词听起来让人不太舒服,但保证一部分请求能够正常响应,总比全部请求都不能响应要好得多。


限流一般都是系统内实现的,常见的限流方式可以分为两类:基于请求限流和基于资源限流。

Ÿ  基于请求限流

基于请求限流指从外部访问的请求角度考虑限流,常见的方式有限制总量和限制时间量。


限制总量的方式是限制某个指标的累积上限,常见的是限制当前系统服务的用户总量。例如,某个直播间限制总用户数上限为100万,超过100万后新的用户无法进入;某个抢购活动的商品数量只有100个,限制参与抢购的用户上限为1万名,1万名以后的用户直接拒绝。限制时间量指限制一段时间内某个指标的上限。例如,1分钟内只允许1万个用户访问,每秒请求峰值最高为10万。


无论限制总量,还是限制时间量,共同的特点都是实现简单,但在实践中面临的主要问题是比较难以找到合适的阈值。例如,系统设定了1分钟1万个用户,但实际上6000个用户的时候系统就扛不住了;也可能达到1分钟1万用户后,其实系统压力还不大,但此时已经开始丢弃用户访问了。


即使找到了合适的阈值,基于请求限流还面临硬件相关的问题。例如,一台32核的机器和64核的机器的处理能力差别很大,阈值是不同的,可能有的技术人员以为简单根据硬件指标进行数学运算就可以得出来,实际上这样是不可行的。64核的机器对比32核的机器,业务处理性能并不是2倍的关系,可能是1.5倍,甚至可能是1.1倍。


为了找到合理的阈值,通常情况下可以采用性能压测来确定阈值,但性能压测也存在覆盖场景有限的问题,可能出现某个性能压测没有覆盖的功能导致系统压力很大;另外一种方式是逐步优化,即先设定一个阈值然后上线观察运行情况,发现不合理就调整阈值。


基于上述的分析,根据阈值来限制访问量的方式更多适应于业务功能比较简单的系统,例如,负载均衡系统、网关系统、抢购系统等。


Ÿ  基于资源限流

基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,即找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源有连接数、文件句柄、线程数、请求队列等。


例如,采用Netty来实现服务器,每个进来的请求都先放入一个队列,业务线程再从队列读取请求进行处理,队列长度最大值为10000,队列满了就拒绝后面的请求;也可以根据CPU的负载或占用率进行限流,当CPU的占用率超过80%的时候就开始拒绝新的请求。


基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力,但实践中设计也面临两个主要的难点:如何确定关键资源和如何确定关键资源的阈值。通常情况下,这也是一个逐步调优的过程,即设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,那么再进行优化。


排队

排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待很长时间,全世界最有名的排队当属12306网站排队了。排队虽然没有直接拒绝用户,但用户等了很长时间后进入系统,体验并不一定比限流好。


由于排队需要临时缓存大量的业务请求,单个系统内部无法缓存这么多数据,一般情况下,排队需要用独立的系统去实现。例如,使用Kafka这类消息队列来缓存用户请求。


如下是1号店的“双11”秒杀排队系统架构。



其基本实现摘录如下:

 

【排队模块】

负责接收用户的抢购请求,将请求以先入先出的方式保存下来。每一个参加秒杀活动的商品保存一个队列,队列的大小可以根据参与秒杀的商品数量(或加点余量)自行定义。


【调度模块】

负责排队模块到服务模块的动态调度,不断检查服务模块,一旦处理能力有空闲,就从排队队列头上把用户访问请求调入服务模块,并负责向服务模块分发请求。这里调度模块扮演一个中介的角色,但不只是传递请求而已,它还担负着调节系统处理能力的重任。我们可以根据服务模块的实际处理能力,动态调节向排队系统拉取请求的速度。


【服务模块】

负责调用真正业务来处理服务,并返回处理结果,调用排队模块的接口回写业务处理结果。



本文小结


  • 异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,多活就是指不同地理位置上的系统都能够提供业务服务。

  • 异地多活虽然功能很强大,但也不是每个业务不管三七二十一都要上异地多。

  • 如果业务规模很大,能够做异地多活的情况下尽量实现异地多活。

  • 异地多活架构可以分为同城异区、跨城异地、跨国异地。

  • 同城异区指的是将业务部署在同一个城市不同区的多个机房。

  • 同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度,这就意味着虽然是两个不同地理位置上的机房,但逻辑上我们可以将它们看作同一个机房。

  • 跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。

  • 跨城异地距离较远带来的网络传输延迟问题,给业务多活架构设计带来了复杂性。

  • 跨国异地指的是业务部署在不同国家的多个机房。

  • 跨国异地主要适应两种场景:为不同地区的用户提供服务,为全球用户提供只读服务。

  • 异地多活设计技巧一:保证核心业务的异地多活。

  • 异地多活设计技巧二:保证核心数据最终一致性。

  • 异地多活设计技巧三:采用多种手段同步数据。

  • 异地多活设计技巧四:只保证绝大部分用户的异地多活。

  • 接口级故障的主要应对方案:降级、熔断、限流、排队。

  • 降级的核心思想就是丢车保帅,优先保证核心业务。

  • 限流指只允许系统能够承受的用户量进来访问,超出系统访问能力的用户将被抛弃。

  • 排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待很长时间。


本文节选自《从零开始学架构:照着做,你也能成为架构师》一书,电子工业出版社出版。


作者李运华,互联网资深技术专家,十多年技术老兵,目前带领多个研发团队,承担架构设计、架构重构、技术团队管理、技术培训等职责;专注于开源技术、系统分析、架构设计,对互联网技术的特点和发展趋势有较深入的研究,对系统解耦、高性能、高可用架构有丰富的经验。

推荐↓↓↓
Web开发
上一篇:微服务架构之「 监控系统 」 下一篇:微服务的历史与陷阱