软件体系架构阅读笔记(八)

1. 全部业务异地多活数据库

“异地多活”是为了保证业务的高可用,但不少朋友在考虑这个“业务”的时候,会不自觉的陷入一个思惟误区:我要保证全部业务的“异地多活”!微信

好比说假设咱们须要作一个“用户子系统”,这个子系统负责“注册”、“登陆”、“用户信息”三个业务。为了支持海量用户,咱们设计了一个“用户分区”的架构,即:正常状况下用户属于某个主分区,每一个分区都有其它数据的备份,用户用邮箱或者手机号注册,路由层拿到邮箱或者手机号后,经过hash计算属于哪一个中心,而后请求对应的业务中心。基本的架构以下:网络

考虑这样一个系统,若是3个业务要同时实现异地多活,咱们会发现以下一些难以解决的问题:session

【注册】架构

A中心注册了用户,数据还未同步到B中心,此时A中心宕机,为了支持注册业务多活,那咱们能够挑选B中心让用户去从新注册。看起来很容易就支持多活了,但仔细思考一下会发现这样作会有问题:一个手机号只能注册一个帐号,A中心的数据没有同步过来,B中心没法判断这个手机号是否重复,若是B中心让用户注册,后来A中心恢复了,发现数据有冲突,怎么解决?其实是没法解决的,由于注册帐号不能说挑选最后一个生效;而若是B中心不支持原本属于A中心的业务进行注册,注册业务的双活又成了空谈。异步

有的朋友可能会说:那我修改业务规则,容许一个手机号注册多个帐号不就能够了么?elasticsearch

这样作是不可行的,相似一个手机号只能注册一个帐号这种规则,是核心业务规则,修改核心业务规则的代价很是大,几乎全部的业务都要从新设计,为了架构设计去改变业务规则,并且是这么核心的业务规则是得不偿失的。性能

【用户信息】线程

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

因为用户信息并无帐号那么关键,一种简单的处理方式是按照时间合并,即:最后修改的生效。业务逻辑上没问题,但实际操做也有一个很关键的坑:怎么保证多个中心全部机器时间绝对一致?在异地多中心的网络下,这个是没法保证的,即便有时间同步也没法彻底保证,只要两个中心的时间偏差超过1s,数据就可能出现混乱,即:先修改的反而生效。

还有一种方式是生成全局惟一递增ID,这个方案的成本很高,由于这个全局惟一递增ID的系统自己又要考虑异地多活,一样涉及数据一致性和冲突的问题。

综合上面的简单分析,咱们能够发现,若是“注册”“登陆”、“用户信息”所有都要支持异地多活的话,其实是挺难的,有的问题甚至是无解的。那这种状况下咱们应该如何考虑“异地多活”的方案设计呢?答案其实很简单:优先实现核心业务的异地多活方案!

对于咱们的这个模拟案例来讲,“登陆”才是最核心的业务,“注册”和“用户信息”虽然也是主要业务,但并不必定要实现异地多活。主要缘由在于业务影响。对于一个日活1000万的业务来讲,天天注册用户多是几万,修改用户信息的可能还不到1万,但登陆用户是1000万,很明显咱们应该保证登陆的异地多活。对于新用户来讲,注册不了影响并不很明显,由于他尚未真正开始业务;用户信息修改也相似,用户暂时修改不了用户信息,对于其业务不会有很大影响,而若是有几百万用户登陆不了,就至关于几百万用户没法使用业务,对业务的影响就很是大了:公司的客服热线很快就被打爆了,微博微信上处处都在传业务宕机,论坛里面处处是在骂娘的用户,那就是互联网大事件了!

而登陆实现“异地多活”偏偏是最简单的,由于每一个中心都有全部用户的帐号和密码信息,用户在哪一个中心均可以登陆。用户在A中心登陆,A中心宕机后,用户到B中心从新登陆便可。

有的朋友可能会问,若是某个用户在A中心修改了密码,此时数据尚未同步到B中心,用户到B中心登陆是没法登陆的,这个怎么处理?这个问题其实就涉及另一个思惟误区了,咱们稍后再谈。

2. 实时一致性

异地多活本质上是经过异地的数据冗余,来保证在极端异常的状况下业务也可以正常提供给用户,所以数据同步是异地多活设计方案的核心,但咱们大部分人在考虑数据同步方案的时候,也会不知不觉的陷入完美主义误区:我要全部数据都实时同步!

数据冗余就要将数据从A地同步到B地,从业务的角度来看是越快越好,最好和本地机房同样的速度最好,但让人头疼的问题正在这里:异地多活理论上就不可能很快,由于这是物理定律决定的,即:光速真空传播是每秒30万千米,在光纤中传输的速度大约是每秒20万千米,再加上传输中的各类网络设备的处理,实际还远远达不到光速的速度。

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

所以异地多活方案面临一个没法完全解决的矛盾:业务上要求数据快速同步,物理上正好作不到数据快速同步,所以全部数据都实时同步,其实是一个没法达到的目标。

既然是没法完全解决的矛盾,那就只能想办法尽可能减小影响。有几种方法能够参考:

尽可能减小异地多活机房的距离,搭建高速网络; 尽可能减小数据同步; 保证最终一致性,不保证明时一致性;

【减小距离:同城多中心】

为了减小两个业务中心的距离,选择在同一个城市不一样的区搭建机房,机房间经过高速网络连通,例如在北京的海定区和通州区各搭建一个机房,两个机房间采用高速光纤网络连通,可以达到近似在一个机房的性能。

这个方案的优点在于对业务几乎没有影响,业务能够无缝的切换到同城多中心方案;缺点就是没法应对例如新奥尔良全城被水淹,或者2003美加大停电这种极端状况。因此即便采用这种方案,也还必须有一个其它城市的业务中心做为备份,最终的方案一样仍是要考虑远距离的数据传输问题。

【减小数据同步】

另一种方式就是减小须要同步的数据。简单来讲就是不重要的数据不要同步,同步后没用的数据不一样步。

之前面的“用户子系统”为例,用户登陆所产生的token或者session信息,数据量很大,但其实并不须要同步到其它业务中心,由于这些数据丢失后从新登陆就能够了。

有的朋友会问:这些数据丢失后要求用户从新登陆,影响用户体验的呀! 确实如此,毕竟须要用户从新输入帐户和密码信息,或者至少要弹出登陆界面让用户点击一次,但相比为了同步全部数据带来的代价,这个影响彻底能够接受,其实这个问题也涉及了一个异地多活设计的典型思惟误区,后面咱们会详细讲到。

【保证最终一致性】

第三种方式就是业务不依赖数据同步的实时性,只要数据最终能一致便可。例如:A机房注册了一个用户,业务上不要求可以在50ms内就同步到全部机房,正常状况下要求5分钟同步到全部机房便可,异常状况下甚至能够容许1小时或者1天后可以一致。

最终一致性在具体实现的时候,还须要根据不一样的数据特征,进行差别化的处理,以知足业务须要。例如对“帐号”信息来讲,若是在A机房新注册的用户5分钟内正好跑到B机房了,此时B机房尚未这个用户的信息,为了保证业务的正确,B机房就须要根据路由规则到A机房请求数据(这种处理方式其实就是后面讲的“二次读取”)。

而对“用户信息”来讲,5分钟后同步也没有问题,也不须要采起其它措施来弥补,但仍是会影响用户体验,即用户看到了旧的用户信息,这个问题怎么解决呢?这个问题实际上也涉及到了一个思惟误区,在最后咱们统一分析。

3. 只使用存储系统的同步功能

数据同步是异地多活方案设计的核心,幸运的是基本上存储系统自己都会有同步的功能,例如MySQL的主备复制、Redis的Cluster功能、elasticsearch的集群功能。这些系统自己的同步功能已经比较强大,可以直接拿来就用,但这也无形中将咱们引入了一个思惟误区:只使用存储系统的同步功能!

既然说存储系统自己就有同步功能,并且同步功能还很强大,为什么说只使用存储系统是一个思惟误区呢?由于虽然绝大部分场景下,存储系统自己的同步功能基本上也够用了,但在某些比较极端的状况下,存储系统自己的同步功能可能难以知足业务需求。

以MySQL为例,MySQL5.1版本的复制是单线程的复制,在网络抖动或者大量数据同步的时候,常常发生延迟较长的问题,短则延迟十几秒,长则可能达到十几分钟。并且即便咱们经过监控的手段知道了MySQL同步时延较长,也难以采起什么措施,只能干等。

Redis又是另一个问题,Redis 3.0以前没有Cluster功能,只有主从复制功能,而为了设计上的简单,Redis主从复制有一个比较大的隐患:从机宕机或者和主机断开链接都须要从新链接主机,从新链接主机都会触发全量的主从复制,这时候主机会生成内存快照,主机依然能够对外提供服务,可是做为读的从机,就没法提供对外服务了,若是数据量大,恢复的时间会至关的长。

综合上述的案例能够看出,存储系统自己自带的同步功能,在某些场景下是没法知足咱们业务须要的。尤为是异地多机房这种部署,各类各样的异常均可能出现,当咱们只考虑存储系统自己的同步功能时,就会发现没法作到真正的异地多活。

解决的方案就是拓开思路,避免只使用存储系统的同步功能,能够将多种手段配合存储系统的同步来使用,甚至能够不采用存储系统的同步方案,改用本身的同步方案。

例如,仍是之前面的“用户子系统”为例,咱们能够采用以下几种方式同步数据:

消息队列方式:对于帐号数据,因为帐号只会建立,不会修改和删除(假设咱们不提供删除功能),咱们能够将帐号数据经过消息队列同步到其它业务中心。

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

存储系统同步方式:对于密码数据,因为用户改密码频率较低,并且用户不可能在1s内连续改屡次密码,因此经过数据库的同步机制将数据复制到其它业务中心便可,用户信息数据和密码相似。

回源读取方式:对于登陆的session数据,因为数据量很大,咱们能够不一样步数据;但当用户在A中心登陆后,而后又在B中心登陆,B中心拿到用户上传的session id后,根据路由判断session属于A中心,直接去A中心请求session数据便可,反之亦然,A中心也能够到B中心去拿取session数据。

从新生成数据方式:对于第4中场景,若是异常状况下,A中心宕机了,B中心请求session数据失败,此时就只能登陆失败,让用户从新在B中心登陆,生成新的session数据。

(注意:以上方案仅仅是示意,实际的设计方案要比这个复杂一些,还有不少细节要考虑)

综合上述的各类措施,最后咱们的“用户子系统”同步方式总体以下:

 

4. 100%可用性

前面咱们在给出每一个思惟误区对应的解决方案的时候,其实都遗留了一些小尾巴:某些场景下咱们没法保证100%的业务可用性,老是会有必定的损失。例如密码不一样步致使没法登陆、用户信息不一样步致使用户看到旧的用户信息等等,这个问题怎么解决?

其实这个问题涉及异地多活设计方案中一个典型的思惟误区:我要保证业务100%可用!但极端状况下就是会丢一部分数据,就是会有一部分数据不能同步,怎么办呢,有没有什么巧妙和神通的办法能作到?

很遗憾,答案是没有!异地多活也没法保证100%的业务可用,这是由物理规律决定的,光速和网络的传播速度、硬盘的读写速度、极端异常状况的不可控等,都是没法100%解决的。因此针对这个思惟误区,个人答案是“忍”!也就是说咱们要忍受这一小部分用户或者业务上的损失,不然原本想为了保证最后的0.01%的用户的可用性,作个完美方案,结果却发现99.99%的用户都保证不了了。

对于某些实时强一致性的业务,实际上受影响的用户会更多,甚至可能达到1/3的用户。以银行转帐这个业务为例,假设小明在北京XX银行开了帐号,若是小明要转帐,必定要北京的银行业务中心是可用的,不然就不容许小明本身转帐。若是不这样的话,假设在北京和上海两个业务中心实现了实时转帐的异地多活,某些异常状况下就可能出现小明只有1万存款,他在北京转给了张三1万,而后又到上海转给了李四1万,两次转帐都成功了。这种漏洞若是被人利用,后果不堪设想。

固然,针对银行转帐这个业务,能够有不少特殊的业务手段来实现异地多活。例如分为“实时转帐”和“转帐申请”。实时转帐就是咱们上述的案例,是没法作到“异地多活”的;但“转帐申请”是能够作到“异地多活”的,即:小明在上海业务中心提交转帐请求,但上海的业务中心并不当即转帐,而是记录这个转帐请求,而后后台异步发起真正的转帐操做,若是此时北京业务中心不可用,转帐请求就能够继续等待重试;假设等待2个小时后北京业务中心恢复了,此时上海业务中心去请求转帐,发现余额不够,这个转帐请求就失败了。小明再登陆上来就会看到转帐申请失败,缘由是“余额不足”。不过须要注意的是“转帐申请”的这种方式虽然有助于实现异地多活,但其实仍是牺牲了用户体验的,对于小明来讲,原本一次操做的事情,须要分为两次:一次提交转帐申请,另一次要确认是否转帐成功。

虽然咱们没法作到100%可用性,但并不意味着咱们什么都不能作,为了让用户内心更好受一些,咱们能够采起一些措施进行安抚或者补偿,例如:

挂公告:说明如今有问题和基本的问题缘由,若是不明确缘由或者不方便说出缘由,能够说“技术哥哥正在紧急处理”比较轻松和有趣的公告。

过后对用户进行补偿:例如送一些业务上可用的代金券、小礼包等,下降用户的抱怨。

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

5. 一句话谈“异地多活”

综合前面的分析,异地多活设计的理念能够总结为一句话:采用多种手段,保证绝大部分用户的核心业务异地多活

文章连接:http://www.uml.org.cn/zjjs/2016120505.asp

相关文章
相关标签/搜索