高可用架构-业务高可用
1 异地多活
避免天灾导致的系统服务器故障
异地:不把所有的服务器放在一个地方
多活:不同地理位置上的系统都能提供服务,活跃
系统是否是异地多活通常满足以下两个标准
- 正常情况,用户无论在哪个地点都能得到正确的业务服务
- 某地异常情况下,用户能访问到其他地方的正常业务系统,且获取正确的业务服务
虽然异地多活很强大,能保证系统高可用的能力更高,但是异地多活是有代价的,不是什么业务都需要异地多活
代价
- 系统复杂度有质的变化,异地多活的架构本身就很复杂
- 成本会升高,多个地区架设机房,是财力物力的证明😊
收益 - 异地多活对于一些业务带来的收益值得去做异地多活:抖音带货直播,服务类的滴滴,支付宝
1.1 异地多活架构
根据地理位置可以分为:
同城异区
、跨城异地
、跨国异地
同城异区
- 部署在同一个城市的不同区
- 目的是可以通过搭建高速网络,让两地机房就如同一个机房的传输速度,可以不用额外设计异地的架构设计,因为可以将这种架构看作是同一个机房的集群架构
- 如果发生天灾等,可能会导致整个异地多活架构变为笑话
- 可以预防机房火灾、停电、空调故障等问题
跨城异地
- 部署在不同城市的多个机房,且之间距离远
- 可以有效应对同城异区中提到的天灾事件
- 且距离远,机房多会导致架构复杂度产生质变
- 网络传输速度、人祸导致(挖断光缆)
- 用于数据一致性要求不太高,且数据变化频率低,且数据可以丢失的业务场景
- 网络延迟会导致很严重的数据不一致情况,没办法从根本解决
跨国异地
- 部署在不同的国家的多个机房,比跨城异地距离更加遥远(延迟更加大)
- 跨国异地和跨城异地等异地多活方案实际应用场景可能有很大不同
- 主要是为不同的地区提供不同的服务,如亚马逊中国是为中国用户服务,亚马逊美国是为美国用户服务,且界面,数据格式,都会有所不同(美元$和人民币¥)
- 只读业务做多活,比如用户搜索内容,此时使用异地多活虽然有延迟,但是影响并不大,只是搜不到实时的资料罢了
1.2 异地多活设计技巧
保证核心业务的异地多活
不要陷入思维误区,要把所有的业务都做到异地多活的做法是错的,我们一般要保证核心业务的异地多活,至于这个“核心”的范围也要明确好
比如注册中,如果要不保证异地多活,就和前面几章提到的存储高可用一样,数据没有同步过去,那么可能会出现数据不一致,以及数据重复等问题,用户在A节点注册后,结果B节点访问不了,就又在B注册,导致用户重复
所以一般这种场景,我们需要去评估哪些功能是核心,需要去保证异地多活。比如登录,注册,用户信息修改等业务,登录才是核心,注册不了只不过是一点点的用户流失,但是一旦登录不上,可能就会骂声四起,即使是前面提到的数据不一致,也只需要我们最后手动,或自动处理这部分脏数据即可
核心数据最终一致性
异地多活本质上还是异地的数据冗余,保证天灾时,有数据可用,所以就会涉及数据的同步,同步速度理论上就不可能很快,物理定律决定的
速度无法解决,那就尽量减少同步慢带来的影响
- 减少异地多活机房的距离,搭建高速网络
- 主要还是要实地考察和信息手机,去评估多地间隔以及防灾等多点的均匀
- 减少数据同步量,只同步核心业务相关数据
- 不重要的数据不同步
- 保证最终一致性,不保证实时一致性
采用多种手段同步数据
数据同步是核心,并且现在的存储系统的同步功能就已经够用了,MySQL的主备复制,Redis的Cluster,ES的集群分区备份,但是我们不能只使用存储系统的同步功能
原因
- 某些极端情况存储系统的同步无法满足要求,我们通过其他的同步手段去弥补,本质上还是和存储系统的同步进行互补
- MySQL复制是单线程复制,在网络抖动和数据大的情况下会发生延迟较长的问题
- Redis的全量复制一般都是RDB的持久化文件去同步,且一旦从机宕机重启,会再次请求主机复制,且从机无法提供读服务,必须等数据同步完成后才能服务
其他方式
- 消息队列方式
- 对于账号数据,可以通过消息队列的方式,同步到各个业务模块
- 二次读取方式
- 消息队列同步延迟时,保底访问B节点没有数据,再去访问A节点,但不能解决A节点遭受灾害后不能提供服务器情况
- 存储系统同步方式
- 对于一些修改频率低的数据,可以通过数据库同步机制复制数据到其他业务中心(这个修改频率至少要低于同步频率)
- 回源读取方式
- 类似于Session数据,存在某一个具体的节点中,就需要我们通过判断当前请求的数据在节点,主动请求这个节点获取数据即可
- 重新生成数据方式
- session在A节点,但是A节点G了,B节点无法获取,此时可以B节点重新生成session
只保证大部分用户的异地多活
前面提到的即使注册了重复的用户,我们也可以去通过自动或手动去解决脏数据,但是此时很有可能用户会因为这个问题无法使用,但是这只是小部分用户,所以我们不要以为我们的异地多活就是要保证自己的核心业务100%可用,这是不可能的
- 有些业务,我们只能损失一部分用户体验而去保证数据的实时一致性,比如转账,异地转账如果是用户本人操作的话,很可能就会出现问题,所以一般会强制在本地开户的银行范围内转账
虽然上述情况会造成用户使用不爽,但是又没有不提供异地转账服务,只不过要转账申请,通过后台控制转账流程去进行,用户虽然有怨言,但是也需要我们进行安抚和赔偿 - 公告
- 事后补偿,代金卷,小礼品(有幸体验到语雀的半年会员补偿)
核心就是采用多种手段,保证大部分用户的核心业务异地多活
1.3 异地多活设计步骤
第一步:业务分级
按一定标准将业务分级,挑选出核心业务,只做核心业务的异地多活,降低成本和复杂度
常见分级标准如下
- 访问量大的业务:用户管理系统访问量大的是登录
- 核心业务:QQ空间和聊天相比,优先保证聊天,因为是核心业务
- 产生大量收入的业务
第二步:数据分类
业务分级后,对核心业务的数据进行分析,用于识别所有的数据和数据特征,方便后续的方案设计
常见的数据特征分析维度
- 数据量:包括数据的新增、修改、删除的量,数据量越大,同步延迟概率越大,复杂度越高
- 唯一性:指是否要求多个异地机房产生同类数据保证唯一性,如分布式id解决全局唯一id的这种唯一性
- 实时性:数据同步的实时性
- 可丢失性:数据是否可丢失,以及丢失后的影响
- 可恢复性:数据能够恢复的难度,对于架构的复杂度有影响
第三步:数据同步
需要根据不同的数据设计不同的同步方案
常见的数据同步方案
- 存储系统同步:例如MySQL的主从同步等,使用简单,主流存储系统都会有
- 消息队列同步:独立的消息队列进行同步,适合无事务或无时序要求的数据
- 重复生成:不进行数据同步,而是让机房自己生成数据,比如cookie和session
第四步:异常处理
因为架构不是100%保证异地多活,所以一旦出现极端异常,还是会有数据异常,这时就需要进行异常处理方案的设计
异常处理的目的
- 问题发生时,避免少量数据异常导致整体不可用
- 问题恢复后,将异常的数据修复和恢复
- 对用户进行安抚,弥补用户损失
常见的异常处理措施
- 多通道同步:采取多种方式进行同步,一条路走不通就走另一条路
- 一般两个通道即可
- 两种通道不要采取一样的网络了解,不然没卵用
- 数据可以重复覆盖
- 同步和访问结合:类似多通道,只不过需要节点主动去访问获取数据进行同步
- 同步和访问不要采用相同的网络链接
- 数据有路由规则时,可以根据数据推断访问哪个节点
- 因为有同步,所以可以先访问本地,没有再主动访问
- 日志记录:记录日志,用于故障恢复后进行数据恢复
- 需要在每个关键操作前后记录日志,且保存在独立的地方,恢复后,拿出日志和数据对比
- 不同的故障级别日志保存要求不同
- 服务器保存日志,数据库保存数据,可以应对单台数据库服务器故障
- 本地独立系统保存日志,应对某业务服务器和数据同时宕机
- 日志异地保存,可以应对机房宕机情况
- 用户补偿:前面提到的不能保证100%不出问题,所以处理问题不仅数据要恢复,用户也要挽回和安抚
- 实际物品:可能需要付出实际的资金
- 游戏物品:损失不大,毕竟是数据😊
2 接口级的故障应对方案
异地多活主要面对的是系统级别的故障,比如,机房故障,节点宕机等,但是发生概率比较小,像接口级别的故障发生率会比较高
表现为
- 业务响应缓慢
- 访问超时
- 访问异常
很大可能就是请求数太多了,负载太高,业务堆积过多,最后出现的现象可能是一会访问正常,一会访问抛出异常,访问很慢
原因可能有 - 内部原因:程序本身编写问题,死循环等
- 外部原因:黑客攻击,访问量过高,第三方请求出问题
接口级故障核心思想和异地多活类似:优先保证核心业务和绝大部分用户
2.1 降级
降级是指将某些业务或者接口的功能降低:可以是只提供部分功能,也可以停掉所有功能如同丢车保帅,优先关闭非必要功能,保证核心功能正常使用资源
- 系统后门降级:系统预留了后门用于降级操作,系统提供降级的URL,访问这个接口后,会根据传输的参数来执行具体的降级策略
- 成本低,节点多的话,还需要一个一个操作,效率低
- 独立降级系统:相当于后门降级自动版,将整个降级做成一个业务,去操作整个系统的降级逻辑
2.2 熔断
熔断虽然和降级的动作很像,都是禁止某些功能的含义,但是降级是应对自身出现故障,而熔断是应对下游/依赖外部系统出现故障的情况
- 熔断实现关键在于需要一个统一的API调用层,方便采样和统计
- 熔断实现的另一个关键在于阈值的设计,比如一分钟内30%的请求响应时间超过1秒,实践中一般先分析确定阈值,再去观察调优
2.3 限流
从系统功能的优先级来考虑应对用户与接口的访问压力,只允许系统能够承受的访问量进入,超出系统访问能力的将会被丢弃
- 丢弃只是为了保证服务器不受到超过自身能力的压力
- 还能保证某些优先级高的接口不被其他低接口的压力所影响(限流低优先级)
- 一般是在系统内实现的,常见的限流方式可以分为基于请求限流、基于资源限流
基于请求限流
从外部访问的请求角度考虑限流,常见方式有限制总量和限制时间量
限制总量:是限制某个指标的累积上限,如:直播间的用户总数限制为100万,超过100万后就无法进入
限制时间量:指限制一段时间内某个指标上限,如:一分钟内只允许一万个用户访问->限制QPS
无论限制总量还是限制时间量,共同特点都是实现简单,主要难点在于找到合适的阈值以及硬件对于阈值的影响,但是一般可以通过压测来找到合适的阈值
基于资源限流
基于请求限流是站在外部考虑,但是基于资源从系统内部考虑,限制系统内部影响性能的关键资源,对其上限进行限制,有连接数,文件句柄,线程数,请求队列等
难点
- 如何确定关键资源以及资源的阈值
2.4 排队
实际是限流的一个变种,排队是让用户等待一段时间来避免直接拒绝用户
- 排队需要临时缓存大量业务请求,一般需要一个独立的系统去进行缓存,因为单个系统或者节点无法缓存这么多数据,例如使用消息队列
12306的排队实现
- 排队模块:负责接收用户的抢购请求,将请求以先入先出的方式保存下来。
- 每一个参加秒杀活动的商品保存一个队列,队列的大小可以根据参与秒杀的商品数量(或加点余量)自行定义。
- 调度模块:负责排队模块到服务模块的动态调度,不断检查服务模块,一旦处理能力有空闲,就从排队队列头上把用户访问请求调入服务模块,并负责向服务模块分发请求。
- 这里调度模块扮演一个中介的角色,但不只是传递请求而已,它还担负着调节系统处理能力的重任。
- 我们可以根据服务模块的实际处理能力,动态调节向排队系统拉取请求的速度。
- 服务模块:负责调用真正业务来处理服务,并返回处理结果,调用排队模块的接口回写业务处理结果。
3 总结
- 异地多活架构的关键点是异地、多活,其中异地指地理位置不同的地方,多活指不同地理位置上的系统都能提供服务
- 异地多活虽然功能强大,但是不是所有业务都要用异地多活
- 如果业务规模很大,能够做异地多活的情况尽量异地多活
- 异地多活架构可以分为同城异区,跨城异地,跨国异地
- 同城异区指的是将业务部署在同一个城市不同区的多个机房。
- 同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度,这就意味着虽然是两个不同地理位置上的机房,但逻辑上我们可以将它们看作同一个机房。
- 跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。
- 跨城异地距离较远带来的网络传输延迟问题,给业务多活架构设计带来了复杂性。
- 跨国异地指的是业务部署在不同国家的多个机房。
- 跨国异地主要适应两种场景:为不同地区的用户提供服务,为全球用户提供只读服务。
- 异地多活设计技巧一:保证核心业务的异地多活。
- 异地多活设计技巧二:保证核心数据最终一致性。
- 异地多活设计技巧三:采用多种手段同步数据。
- 异地多活设计技巧四:只保证绝大部分用户的异地多活。
- 接口级故障的主要应对方案:降级、熔断、限流、排队。
- 降级的核心思想就是丢车保帅,优先保证核心业务。
- 限流指只允许系统能够承受的用户量进来访问,超出系统访问能力的用户将被抛弃。
- 排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待很长时间。