无论是高可用计算架构,还是高可用存储架构,其本质的设计目的都是为了解决部分服务器故障的场景下,如何保证系统能够继续提供服务。但在一些极端场景下,有可能所有服务器都出现故障。例如,典型的有机房断电、机房火灾、地震、水灾……这些极端情况会导致某个系统所有服务器都故障,或者业务整体瘫痪,而且即使有其他地区的备份,把备份业务系统全部恢复到能够正常提供业务,花费的时间也比较长,可能是半小时,也可能是12小时。因为备份系统平时不对外提供服务,可能会存在很多隐藏的问题没有发现。如果业务期望达到即使在此类灾难性故障的情况下,业务也不受影响,或者在几分钟内就能够很快恢复,那么就需要设计异地多活架构。
顾名思义,异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。判断一个系统是否符合异地多活,需要满足两个标准:
与“活”对应的是字是“备”,备是备份,正常情况下对外是不提供服务的,如果需要提供服务,则需要大量的人工干预和操作,花费大量的时间才能让“备”变成“活”。
单纯从异地多活的描述来看,异地多活很强大,能够保证在灾难的情况下业务都不受影响。那是不是意味着不管什么业务,我们都要去实现异地多活架构呢?其实不然,因为实现异地多活架构不是没有代价的,相反其代价很高,具体表现为:
因此,异地多活虽然功能很强大,但也不是每个业务不管三七二十一都要上异地多活。例如,常见的新闻网站、企业内部的IT系统、游戏、博客站点等,如果无法承受异地多活带来的复杂度和成本,是可以不做异地多活的,只需要做异地备份即可。因为这类业务系统即使中断,对用户的影响并不会很大,例如,A新闻网站看不了,用户换个新闻网站即可。而共享单车、滴滴出行、支付宝、微信这类业务,就需要做异地多活了,这类业务系统中断后,对用户的影响很大。例如,支付宝用不了,就没法买东西了;滴滴用不了,用户就打不到车了。
当然,如果业务规模很大,能够做异地多活的情况下还是尽量。首先,这样能够在异常的场景下给用户提供更好的体验;其次,业务规模很大肯定会伴随衍生的收入,例如广告收入,异地多活能够减少异常场景带来的收入损失。同样以新闻网站为例,虽然从业务的角度来看,新闻类网站对用户影响不大,反正用户也可以从其他地方看到基本相同的新闻,甚至用户几个小时不看新闻也没什么问题。但是从网站本身来看,几个小时不可访问肯定会影响用户对网站的口碑;其次几个小时不可访问,网站上的广告收入损失也会很大。
根据地理位置上的距离来划分,异地多活架构可以分为同城异区、跨城异地、跨国异地。接下来我详细解释一下每一种架构的细节与优缺点。
对于以上这种假设场景,虽然普通用户很难这样自如地操作,但如果真的这么做,被黑客发现后,后果不堪设想。正因为如此,支付宝等金融相关的系统,对余额这类数据,一般不会做跨城异地的多活架构,而只能采用同城异区这种架构。
而对数据一致性要求不那么高,或者数据不怎么改变,或者即使数据丢失影响也不大的业务,跨城异地多活就能够派上用场了。例如,用户登录(数据不一致时用户重新登录即可)、新闻类网站(一天内的新闻数据变化较少)、微博类网站(丢失用户发布的微博或者评论影响不大),这些业务采用跨城异地多活,能够很好地应对极端灾难的场景。
跨国异地指的是业务部署在不同国家的多个机房。相比跨城异地,跨国异地的距离就更远了,因此数据同步的延时会更长,正常情况下可能就有几秒钟了。这种程度的延迟已经无法满足异地多活标准的第一条:“正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务”。例如,假设有一个微博类网站,分别在中国的上海和美国的纽约都建了机房,用户A在上海机房发表了一篇微博,此时如果他的一个关注者B用户访问到美国的机房,很可能无法看到用户A刚刚发表的微博。虽然跨城异地也会有此类同步延时问题,但正常情况下几十毫秒的延时对用户来说基本无感知的;而延时达到几秒钟就感觉比较明显了。
因此,跨国异地的“多活”,和跨城异地的“多活”,实际的含义并不完全一致。跨国异地多活的主要应用场景一般有这几种情况:
例如,亚马逊中国是为中国用户服务的,而亚马逊美国是为美国用户服务的,亚马逊中国的用户如果访问美国亚马逊,是无法用亚马逊中国的账号登录美国亚马逊的。
例如,谷歌的搜索业务,由于用户搜索资料时,这些资料都已经存在于谷歌的搜索引擎上面,无论是访问英国谷歌,还是访问美国谷歌,搜索结果基本相同,并且对用户来说,也不需要搜索到最新的实时资料,跨国异地的几秒钟网络延迟,对搜索结果是没有什么影响的。
每个架构的关键点:
基于这个分析,跨城异地多活是架构设计复杂度最高的一种,接下来将介绍跨城异地多活架构设计的一些技巧
“异地多活”是为了保证业务的高可用,但很多架构师在考虑这个“业务”时,会不自觉地陷入一个思维误区:我要保证所有业务都能“异地多活”!
假设我们需要做一个“用户子系统”,这个子系统负责“注册”“登录”“用户信息”三个业务。为了支持海量用户,我们设计了一个“用户分区”的架构,即正常情况下用户属于某个主分区,每个分区都有其他数据的备份,用户用邮箱或者手机号注册,路由层拿到邮箱或者手机号后,通过Hash计算属于哪个中心,然后请求对应的业务中心。基本的架构如下:
这样一个系统,如果3个业务要同时实现异地多活,会发现这些难以解决的问题:
综合上面的简单分析可以发现,如果“注册”“登录”“用户信息”全部都要支持异地多活,实际上是挺难的,有的问题甚至是无解的。那这种情况下我们应该如何考虑“异地多活”的架构设计呢?答案其实很简单:优先实现核心业务的异地多活架构!
对于这个模拟案例来说,“登录”才是最核心的业务,“注册”和“用户信息”虽然也是主要业务,但并不一定要实现异地多活,主要原因在于业务影响不同。对于一个日活1000万的业务来说,每天注册用户可能是几万,修改用户信息的可能还不到1万,但登录用户是1000万,很明显我们应该保证登录的异地多活。
对于新用户来说,注册不了的影响并不明显,因为他还没有真正开始使用业务。用户信息修改也类似,暂时修改不了用户信息,对于其业务不会有很大影响。而如果有几百万用户登录不了,就相当于几百万用户无法使用业务,对业务的影响就非常大了:公司的客服热线很快就被打爆,微博、微信上到处都在传业务宕机,论坛里面到处是抱怨的用户,那就是互联网大事件了!
而登录实现“异地多活”恰恰是最简单的,因为每个中心都有所有用户的账号和密码信息,用户在哪个中心都可以登录。用户在A中心登录,A中心宕机后,用户到B中心重新登录即可。
如果某个用户在A中心修改了密码,此时数据还没有同步到B中心,用户到B中心登录是无法登录的,这个怎么处理?这个问题其实就涉及另外一个设计技巧了,稍后再谈。
异地多活本质上是通过异地的数据冗余,来保证在极端异常的情况下业务也能够正常提供给用户,因此数据同步是异地多活架构设计的核心。但大部分架构师在考虑数据同步方案时,会不知不觉地陷入完美主义误区:我要所有数据都实时同步!
数据冗余是要将数据从A地同步到B地,从业务的角度来看是越快越好,最好和本地机房一样的速度最好。但让人头疼的问题正在这里:异地多活理论上就不可能很快,因为这是物理定律决定的。
因此异地多活架构面临一个无法彻底解决的矛盾:业务上要求数据快速同步,物理上正好做不到数据快速同步,因此所有数据都实时同步,实际上是一个无法达到的目标。
既然是无法彻底解决的矛盾,那就只能想办法尽量减少影响。有几种方法可以参考:
数据同步是异地多活架构设计的核心,幸运的是基本上存储系统本身都会有同步的功能。例如,MySQL的主备复制、Redis的Cluster功能、Elasticsearch的集群功能。这些系统本身的同步功能已经比较强大,能够直接拿来就用,但这也无形中将我们引入了一个思维误区:只使用存储系统的同步功能!
既然说存储系统本身就有同步功能,而且同步功能还很强大,为何说只使用存储系统是一个思维误区呢?因为虽然绝大部分场景下,存储系统本身的同步功能基本上也够用了,但在某些比较极端的情况下,存储系统本身的同步功能可能难以满足业务需求。
以MySQL为例,MySQL 5.1版本的复制是单线程的复制,在网络抖动或者大量数据同步时,经常发生延迟较长的问题,短则延迟十几秒,长则可能达到十几分钟。而且即使我们通过监控的手段知道了MySQL同步时延较长,也难以采取什么措施,只能干等。
Redis又是另外一个问题,Redis 3.0之前没有Cluster功能,只有主从复制功能,而为了设计上的简单,Redis 2.8之前的版本,主从复制有一个比较大的隐患:从机宕机或者和主机断开连接都需要重新连接主机,重新连接主机都会触发全量的主从复制。这时主机会生成内存快照,主机依然可以对外提供服务,但是作为读的从机,就无法提供对外服务了,如果数据量大,恢复的时间会相当长。
综合上面的案例可以看出,存储系统本身自带的同步功能,在某些场景下是无法满足业务需要的。尤其是异地多机房这种部署,各种各样的异常情况都可能出现,当我们只考虑存储系统本身的同步功能时,就会发现无法做到真正的异地多活。
解决的方案就是拓开思路,避免只使用存储系统的同步功能,可以将多种手段配合存储系统的同步来使用,甚至可以不采用存储系统的同步方案,改用自己的同步方案。
还是以前面的“用户子系统”为例,我们可以采用如下几种方式同步数据:
注意:以上方案仅仅是示意,实际的设计方案要比这个复杂一些,还有很多细节要考虑。
综合上述的各种措施,最后“用户子系统”同步方式整体如下:
前面在给出每个思维误区对应的解决方案时,留下了几个小尾巴:某些场景下我们无法保证100%的业务可用性,总是会有一定的损失。例如,密码不同步导致无法登录、用户信息不同步导致用户看到旧的信息等,这个问题怎么解决呢?
其实这个问题涉及异地多活架构设计中一个典型的思维误区:我要保证业务100%可用!但极端情况下就是会丢一部分数据,就是会有一部分数据不能同步,有没有什么巧妙能做到100%可用呢?
很遗憾,答案是没有!异地多活也无法保证100%的业务可用,这是由物理规律决定的,光速和网络的传播速度、硬盘的读写速度、极端异常情况的不可控等,都是无法100%解决的。所以针对这个思维误区,我的答案是“忍”!也就是说我们要忍受这一小部分用户或者业务上的损失,否则本来想为了保证最后的0.01%的用户的可用性,做一个完美方案,结果却发现99.99%的用户都保证不了了。
对于某些实时强一致性的业务,实际上受影响的用户会更多,甚至可能达到1/3的用户。以银行转账这个业务为例,假设小明在北京XX银行开了账号,如果小明要转账,一定要北京的银行业务中心才可用,否则就不允许小明自己转账。如果不这样的话,假设在北京和上海两个业务中心实现了实时转账的异地多活,某些异常情况下就可能出现小明只有1万元存款,他在北京转给了张三1万元,然后又到上海转给了李四1万元,两次转账都成功了。这种漏洞如果被人利用,后果不堪设想。
当然,针对银行转账这个业务,虽然无法做到“实时转账”的异地多活,但可以通过特殊的业务手段让转账业务也能实现异地多活。例如,转账业务除了“实时转账”外,还提供“转账申请”业务,即小明在上海业务中心提交转账请求,但上海的业务中心并不立即转账,而是记录这个转账请求,然后后台异步发起真正的转账操作,如果此时北京业务中心不可用,转账请求就可以继续等待重试;假设等待2个小时后北京业务中心恢复了,此时上海业务中心去请求转账,发现余额不够,这个转账请求就失败了。小明再登录上来就会看到转账申请失败,原因是“余额不足”。
不过需要注意的是“转账申请”的这种方式虽然有助于实现异地多活,但其实还是牺牲了用户体验的,对于小明来说,本来一次操作的事情,需要分为两次:一次提交转账申请,另外一次是要确认是否转账成功。
虽然我们无法做到100%可用性,但并不意味着我们什么都不能做,为了让用户心里更好受一些,我们可以采取一些措施进行安抚或者补偿,例如:
核心思想
异地多活设计的理念可以总结为一句话:采用多种手段,保证绝大部分用户的核心业务异地多活!
按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。
常见的分级标准有下面几种:
以我们一直在举例的用户管理系统为例,“登录”业务符合“访问量大的业务”和“核心业务”这两条标准,因此我们将登录业务作为核心业务。
挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。
常见的数据特征分析维度有:
我们同样以用户管理系统的登录业务为例,简单分析如下表所示。
确定数据的特点后,我们可以根据不同的数据设计不同的同步方案。常见的数据同步方案有:
我们同样以用户管理系统的登录业务为例,针对不同的数据特点设计不同的同步方案,如下表所示。
第4步:异常处理
无论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的。例如,同步延迟、数据丢失、数据不一致等。异常处理就是假设在出现这些问题时,系统将采取什么措施来应对。异常处理主要有以下几个目的:
常见的异常处理措施有这几类:
1. 多通道同步
多通道同步的含义是采取多种方式来进行数据同步,其中某条通道故障的情况下,系统可以通过其他方式来进行同步,这种方式可以应对同步通道处故障的情况。
以用户管理系统中的用户账号数据为例,我们的设计方案一开始挑选了消息队列的方式进行同步,考虑异常情况下,消息队列同步通道可能中断,也可能延迟很严重;为了保证新注册账号能够快速同步到异地机房,我们再增加一种MySQL同步这种方式作为备份。这样针对用户账号数据同步,系统就有两种同步方式:MySQL主从同步和消息队列同步。除非两个通道同时故障,否则用户账号数据在其中一个通道异常的情况下,能够通过另外一个通道继续同步到异地机房,如下图所示。
多通道同步设计的方案关键点有:
2. 同步和访问结合
这里的访问指异地机房通过系统的接口来进行数据访问。例如业务部署在异地两个机房A和B,B机房的业务系统通过接口来访问A机房的系统获取账号信息,如下图所示。
同步和访问结合方案的设计关键点有:
3. 日志记录
日志记录主要用于用户故障恢复后对数据进行恢复,其主要方式是每个关键操作前后都记录相关一条日志,然后将日志保存在一个独立的地方,当故障恢复后,拿出日志跟数据进行对比,对数据进行修复。
为了应对不同级别的故障,日志保存的要求也不一样,常见的日志保存方式有:
上面不同的日志保存方式,应对的故障越严重,方案本身的复杂度和成本就会越高,实际选择时需要综合考虑成本和收益情况。
4. 用户补偿
无论采用什么样的异常处理措施,都只能最大限度地降低受到影响的范围和程度,无法完全做到没有任何影响。例如,双同步通道有可能同时出现故障、日志记录方案本身日志也可能丢失。因此,无论多么完美的方案,故障的场景下总是可能有一小部分用户业务上出问题,系统无法弥补这部分用户的损失。但我们可以采用人工的方式对用户进行补偿,弥补用户损失,培养用户的忠诚度。简单来说,系统的方案是为了保证99.99%的用户在故障的场景下业务不受影响,人工的补偿是为了弥补0.01%的用户的损失。
常见的补偿措施有送用户代金券、礼包、礼品、红包等,有时为了赢得用户口碑,付出的成本可能还会比较大,但综合最终的收益来看还是很值得的。例如暴雪《炉石传说》2017年回档故障,暴雪给每个用户大约价值人民币200元的补偿,结果玩家都求暴雪再来一次回档,形象地说明了玩家对暴雪补偿的充分认可。
只要在2017年1月18日18点之前登录过国服《炉石传说》的玩家,均可获得与25卡牌包等值的补偿,具体如下:
来源:小马过河 原文:http://t.cn/EKx65SB 题图:来自谷歌图片搜索 版权:本文版权归原作者所有 投稿:欢迎投稿,投稿邮箱: editor@hi-linux.com
今日思想
少关心别人的逸闻私事,多留意别人的思路观点。
—— 居里夫人