公众号阿里技术(ID:ali_tech)
什么是高可用
无论是一个域,一个 BG,还是一个站点,虽然范围有大有小,对象有所不同,但其高可用的理念都是相通的,今天将自己对高可用的一点点思考以及总结的“nPRT 公式”分享给大家。
本文采用“高可用是什么,为什么要高可用,怎么做高可用,为什么这么做,软件风险又在哪里”的逻辑来介绍。
高可用是一种控制风险的能力
高可用是一种面向风险设计,使系统具备控制风险,提供更高的可用性的能力。
为什么要高可用
对于一个公司而言,“为什么要高可用”可以完整理解为“公司为什么要(做系统)高可用”。
以公司为对象,从内看包括:人,软件(物),硬件(物);从外看包括:客户,股东,社会;从自身看包括:公司。
高可用的大前提,所有事物都不是 100% 可靠的:
所有事物都是变化的(唯一不变的是变化)。
所有变化的都不是 100% 可靠的。
结论:所有事物都不是 100% 可靠的。
内因,人、物都不是 100% 可靠的:
从人的层面:人都是有可能犯错的。
从软件层面:软件都是有可能有 Bug 的。
从硬件层面:硬件都是有可能会坏的。
从概率学角度分析,凡是有可能会出错的,只要变化次数足够多,最终出错的概率会无限趋向于 1。
外因,无高可用,对外影响面是很大的:
从客户角度:无高可用,客户服务可能会中断。
从股东层面:无高可用,股价可能会下跌。
从社会角度:无高可用,社会秩序可能受影响。
根因(本质):控制风险。
从公司自身角度:控制风险,保障公司价值,避免伤及根本。
如何做高可用
如何做高可用,本质上就是:如何控制风险。
风险相关概念
风险:指未来会发生危害的一种可能性,但实际未发生,记为r。
故障:指已发生或正在发生危害的一种事实,是风险变现实的结果。
风险概率:指一个风险变故障的概率。用它来表示风险触发为故障的难易程度,记为 P®。
故障影响范围:指在单位时间内,一个故障造成的危害影响,记为 R®。
故障影响时长:指一个故障持续的时间,记为 T®。
故障影响面:指一个故障影响范围乘以故障影响时长的总和。这里用故障影响面来表示故障总的危害程度,记为 F®。
风险期望:指每个风险变故障的概率乘以每个风险变故障后的故障影响面的总和。这里用风险期望来表示风险的潜在危害程度,记为 E®。
风险期望的公式
根据上节的定义,可以推导出风险期望的公式如下:
r 代表风险,风险期望会随着风险的数量 n 和每个风险的 P、R、T 下降而下降,简称 nPRT 公式。(注:如果要引用该公式请注明出处。)
控制风险的 4 大因素(nPRT)
①减少风险数量,n
从源头远离风险,做到与风险载体无连接,无关系;那么该风险概率就是0,也不关心该风险发生后的故障影响面是大是小,完全不关心。
例如:重大节日活动,施行全站封网,变更的数量就会得到一个明显的下降,就是典型的减少风险数量。
例如:系统 A 完全不依赖 Oracle,那系统 A 就不用关心 Oracle 的任何风险,哪怕美国总统突然紧急宣布 Oracle 立即立刻禁止在中国使用,系统 A 也无所谓。
例如:最近新冠大流行,人传人很可怕,如果你今天选择不上班不出门,那你今天就不用担心被外面的行人和同事传染。
②降低风险变故障的概率(即:增加风险变故障的难度),P
把风险当成一个对象看待,给它层层设卡,增加风险变故障的门槛和难度,不要再让“不小心多了一个空格或字符,系统就挂了”这种惨案轻易出现。
例如:人员 B 要对系统 C 进行变更,可以对人员 B 增加变更认证考试,对变更内容要求线下(或仿真)测试,对变更内容进行 CR,系统 C 提供变更效果预览能力(类似监控模式或试运行)。
万一人员 B 想恶意变更搞破坏,还可以增加非同人复核,系统C可以增加防错设计进行保护等等。
例如:以新冠为例,带口罩,勤洗手,多通风等就可以降低染上新冠的概率。
③减小故障影响范围,R
以大拆小,将一个整体拆分成 N 个小的个体,每个个体之间进行相互隔离,单个个体出问题仅影响单个个体,实现小而美。
例如:分布式架构就是这个的典范,集中式一损俱损,分布式一损即 N 分之一损。
例如:以新冠为例,网格化管理,各省或市间的流动进行限制,跨省必须核酸+隔离 14 天,有效控制新冠的传播范围。
④缩短故障影响时长,T
故障影响时长由故障发现时间和故障止血时间决定,所以要早发现早止血。
发现方式分为:事前的预警,事后的告警。尽可能朝事前预警去做,给止血争取时间甚至将风险扼杀在摇篮中。
止血方式分为:切换,回滚,扩容,降级 or 限流,BUG 修复等。故障出现时第一优先原则为快速止血(如切换、回滚、扩容),严禁去定位根因;当无法快速止血时以少流血为第二优先原则,如降级、限流。
止血效率:自动 vs 人工 ;一键化 vs 多步操作。尽可能用自动化去代替人工操作,若人工操作时尽量实现一键化,提升止血速度。
例如:对于容量水位,可以在警戒线之前划一条预警线,提前预警,从容应对。
例如:分布式应用集群,任何一台应用服务器有问题时,负载均衡会通过心跳检查自动把有问题的应用服务器剔除,将请求转发给其他(热)备份冗余的服务器上。
例如:以新冠为例,但由于每个生命都是独一无二的,没有办法切换,也没有办法回滚,也不能降级(涉及人道主义),只能对症下药慢慢治疗。
高可用架构设计的 7 大核心原则
根据 nPRT 公式,在高可用架构设计时有以下 7 个核心原则:
①少依赖原则:能不依赖的,尽可能不依赖,越少越好(n)
由于所有事物都不是 100% 可靠的,当 2 个事物之间有了关系,那么就会相互影响,就互为对方的一个风险,一个出问题可能会影响另外一个。我们统一用依赖来泛指这里的“关系”。
例如:一个系统同时依赖 Oracle,MySQL,OB 三种关系型数据库,少依赖原则是改成仅依赖最成熟稳定的 OB,不依赖 Oracle 和 MySQL。
什么场景适合多依赖?当引入依赖(n 变大)可以减小 PRT 中的一个或多个,且使 E® 整体下降时。
例如:为解决 DB 风险,引入分布式缓存,只要 2 者不同时挂的时候依然可用。
②弱依赖原则:一定要依赖的,尽可能弱依赖,越弱越好§
事物 a 强依赖事物 b,一旦 b 出问题时,那么 a 也会出问题,一损俱损。所以任何强依赖都要尽可能的转化成弱依赖,可以直接降低出问题的概率。
例如:交易核心链路在交易成功后要要给用户发放积分权益;交易核心系统需要依赖积分权益系统,好的方式是采用弱依赖,使用异步化的方式,这样积分权益系统不可用时,大概率不会影响交易核心链路。
③分散原则:鸡蛋不要放一个篮子,分散风险®
打散拆分成 N 份;避免全局只有 1 份,否则一有问题影响范围就是 100%。
例如:所有交易数据都放在同一个库同一张表里面,万一这个库挂了,此时影响所有交易。
例如:将自己所有的钱买了同一只股票,万一这只股票是乐视就惨了。
④均衡原则:均匀分散风险,避免不均衡®
最好 N 份中的每份都是均衡的;避免某个份额过大,否则过大的那份一有问题就影响范围过大了。
例如:xx 应用集群有 1000 台,但由于引流组件 Bug,导致所有流量引到了其中 100 台上面,导致负载严重不均衡,最后因负载无法扛着全面崩溃。类似重大故障已经发生了多次。
例如:将自己所有的钱买了 10 只股票,其中一只占比 99%,万一这只股票是乐视就惨了。
⑤隔离原则:控制风险不扩散,不放大®
每份之间是相互隔离的;避免一份有问题影响其他的也有问题,传播扩散了影响范围。
例如:交易数据拆分成 10 库 100 表,但是部署在同一台物理机上;万一某张表有一条大 SQL 把网卡打满了,那 10 库 100 表都会受影响。
例如:将自己所有的钱均分买了 10 只股票,每只都占 10%,但 10 只都是乐视系的。
例如:古代赤壁之战就是一个典型的反面例子,铁锁连船导致隔离性被破坏,一把大火烧了 80w 大军。
隔离是有级别的,隔离级别越高,风险传播扩散的难度就越大,容灾能力越强。
例如:一个应用集群由 N 台服务器组成,部署在同一台物理机上,或同一个机房的不同物理机上,或同一个城市的不同机房里,或不同城市里,不同的部署代表不同的容灾能力。
例如:人类由无数人组成,生活在同一个地球的不同洲上,这意味着人类不具备星球级别的隔离能力,当地球出现毁灭性影响时,人类是不具备容灾的。
隔离原则是一个极其重要的原则,它是前面 4 个原则的前提。
没有做好隔离,前面 4 个原则都是脆弱的,风险很容易传播扩散开,破坏前面 4 个原则的效果。
大量真实系统故障是因为隔离性做得不好导致的,如:线下影响线上,离线影响在线,预发影响生产,一条烂 SQL 影响整个库(或整个集群)等等。
分散,均衡,隔离是控制风险影响范围的 3 个核心原则。打散拆分成 N 份,每一份都是均衡的,且相互隔离,一份有问题,影响范围为 1/N。
⑥无单点原则:要有冗余或其他版本,做到有路可退(T)
快速止血的方式是切换,回滚,扩容等;回滚和扩容属于特殊的切换,回滚指的是切换到某个版本,扩容指的是将流量切换到新扩容的机器上。
切换得有地方可切才行,所以不能有单点(这里特指强依赖的单点,弱依赖的可以降级),要有冗余备份或其他版本;单点会限制整体的可靠性。
假设单点的可靠性假设是 99.99%,它要提升到 99.999% 是非常困难的,但是如果无单点而是依赖 2 个(1 个挂掉没有关系,只要不同时挂就行),那整体可靠性就是 99.% 会有质的提升。
单点故障会导致无法快速止血,拉长整个止血时间,去单点至关重要。这里的单点不仅仅指的是系统节点,也包含人员,如订阅告警的人,应急的人等等。
对于(重要)数据节点,必须满足无单点原则,否则极端情况下可能造成数据永久丢失,永远无法恢复;(重要)数据节点满足无单点原则后,保障数据一致性比可用性要求更重要。
例如:一个商户仅支持一个支付渠道,就是典型的单点,万一这个支付渠道挂了就不能支付了。
例如:一个家庭的所有收入仅依赖父亲一个的薪资收入,万一这个父亲病了,就没有收入了。
无单点原则和分散原则的区别:
当节点无状态的情况下,打散拆分成 N 份,每份都是相同的功能,互为冗余,即:节点无状态情况下,分散原则和无单点原则等价,满足一个即可。
当节点有状态的情况下,打散拆分成 N 份,每份都是不相同的,每份都没有冗余,需要针对每份再做冗余,即:节点有状态情况下,既要满足分散原则又要满足单点原则。
⑦自我保护原则:少流血,牺牲一部分,保护另外一部分(P&R&T)
外部的输入都不是 100% 可靠的,有时候是无意的错误,有时候甚至是恶意的破坏,因此针对外部输入要有防错设计,给自己多一些保护。
极端情况下可能无法(快速)止血,可以考虑少流血,牺牲一部分保护另外一部分。例如:限流,降级等。
例如:大促峰值期间,一般会提前降级掉很多功能,同时限流,主要是为了保护峰值绝大部分人的交易支付体验。
例如:人体在失血过多或疼痛过度时就会触发休克现象,这也是一种典型的自我保护机制。
软件风险在何方
前面介绍了控制风险的方法,回到软件系统这个领域,它的风险又在哪里?
以软件系统为对象,从内看包括:计算系统和存储系统;从外看包括:人员,硬件,上游系统,下游系统;以及(隐含的)时间。
由于每个对象都是由其他对象组成的,因此每个对象还可以继续往细分解(理论上可以无限分解下去),上面的分解方式主要是为了简化理解。
软件系统风险的来源
风险源于(有危害的)变化,一个对象的风险来源于所有跟它有关系的对象的(有危害的)变化。
因此,软件系统风险的来源,分为以下 7 大类:
①计算系统变化:运行变慢,运行错误
系统运行所依赖的服务器资源(如 CPU,MEM,IO 等),应用资源(RPC 线程数,DB 连接数等),业务资源(业务 ID 满了,余额不足,业务额度不够等)的负载等都会影响系统运行的风险期望。
②存储系统变化:运行变慢,运行错误,数据错误
系统运行所依赖的服务器资源(如 CPU,MEM,IO 等),存储资源(并发数等),数据资源(单库容量,单表容量等)的负载和数据一致性等都会影响存储系统运行的风险期望。
③人的变化:变更出错
变更人员的数量,安全生产意识,熟练程度,变更的数量,变更的方式等都会影响变更的风险期望。
由于变更的人多,变更的次数也多,导致变更成为蚂蚁所有故障来源里的 TOP1,这也是为什么“变更三板斧”这么出名的原因。
“变更三板斧”正确的排序应该是“可灰度,可监控,可应急”;可灰度代表的是 R,可监控和可应急代表的是 T。
思考:如果变更三板斧让你再加一板斧,你觉得应该是什么?
④硬件变化:损坏
硬件的数量,质量,使用年限,保养等都会影响硬件的风险期望,硬件损坏会影响上层软件系统不可用。
⑤上游变化:请求变大
请求分为 3 个维度:(由无数 API 汇集而成的)网络流量,(由无数 KEY 请求组成的)API,KEY。
网络流量过大会造成网络堵塞,影响网络通道中的所有网络流量请求。
API 请求过大会造成对应服务集群过载,影响整个服务机器上的所有 API 请求,甚至往外传播。
KEY 请求过大(俗称“热点 KEY”)会造成单机过载,影响单机上所有 KEY 请求,甚至往外传播。
所以大促保障的时候,不仅仅是关注核心 API 的容量保障,还需要考虑网络流量和热点 KEY。
⑥下游变化:响应变慢,响应错误
下游服务的数量,服务等级,服务可用率等影响下游服务的风险期望。下游响应变慢可能会拖慢上游,下游响应错误可能会影响上游运行结果。
⑦时间变化:时间到期
时间到期往往被人忽视,但它往往具有突然性和全局破坏性,一旦时间到期触发故障会导致非常被动,所以要提前识别,尽早预警,如:秘钥到期,证书到期,费用到期,跨时区,跨年,跨月,跨日等。
例如:2019 年日本运营商软银因证书到期引发 3000w 用户长达 4 小时通信中断。
以上每一大类风险都可以基于 nPRT 公式进行逐一分析处理。
风险的数量:一生三,三生万物
任何一个事物既是由其他事物组成的又是其他事物的组成部分,无限循环下去;一生三,三生万物,风险的数量是无穷无尽的。
向内看,内含内,可以无限小下去;当原子粒度的问题传播开时,也可能影响软件系统的可用性,就像 100 纳米的新冠病毒就可以影响人体的可用性一样。
向外看,外有外,可以无限大下去;当太阳系毁灭,软件系统的可用性自然就不复存在。
虽然风险无穷无尽,但是只要我们对风险多一些了解,根据控制风险的一些理念和原则,还是可以更好的降低风险期望。
谈一谈敬畏之心:
我们对世界的认知是有限的,这也让我们少了许多恐惧,同时也让我们少了一些敬畏之心。
我们真正要敬畏的不是处罚条例,而是我们不知道的,以及我们不知道我们不知道。
总结
所有事物都是变化的。
所有事物都不是 100% 可靠的。
因此才有了风险,风险是不可见的,可见的是故障。
风险是不能消灭光的,但是可以远离,可以减少。
故障是不可避免的,但是可以推迟,可以缩小影响范围,缩短影响时间。
nPRT 公式不仅仅适用于软件系统风险,也适用于其他风险领域,希望对大家有用。
实际案例
我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。
保证系统高可用,架构设计的核心准则是:冗余。
有了冗余之后,还不够,每次出现故障需要人工介入恢复势必会增加系统的不可服务实践。所以,又往往是通过“自动故障转移”来实现系统的高可用。
接下来我们看下典型互联网架构中,如何通过冗余+自动故障转移来保证系统的高可用特性。
常见的互联网分层架构
常见互联网分布式架构如上,分为:
(1)客户端层:典型调用方是浏览器browser或者手机应用APP
(2)反向代理层:系统入口,反向代理
(3)站点应用层:实现核心应用逻辑,返回html或者json
(4)服务层:如果实现了服务化,就有这一层
(5)数据-缓存层:缓存加速访问存储
(6)数据-数据库层:数据库固化数据存储
整个系统的高可用,又是通过每一层的冗余+自动故障转移来综合实现的。
分层高可用架构实践
【客户端层->反向代理层】的高可用
【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余来实现的。以nginx为例:有两台nginx,一台对线上提供服务,另一台冗余以保证高可用,常见的实践是keepalived存活探测,相同virtual IP提供服务。
自动故障转移:当nginx挂了的时候,keepalived能够探测到,会自动的进行故障转移,将流量自动迁移到shadow-nginx,由于使用的是相同的virtual IP,这个切换过程对调用方是透明的。
【反向代理层->站点层】的高可用
【反向代理层】到【站点层】的高可用,是通过站点层的冗余来实现的。假设反向代理层是nginx,nginx.conf里能够配置多个web后端,并且nginx能够探测到多个后端的存活性。
自动故障转移:当web-server挂了的时候,nginx能够探测到,会自动的进行故障转移,将流量自动迁移到其他的web-server,整个过程由nginx自动完成,对调用方是透明的。
【站点层->服务层】的高可用
【站点层】到【服务层】的高可用,是通过服务层的冗余来实现的。“服务连接池”会建立与下游服务多个连接,每次请求会“随机”选取连接来访问下游服务。
自动故障转移:当service挂了的时候,service-connection-pool能够探测到,会自动的进行故障转移,将流量自动迁移到其他的service,整个过程由连接池自动完成,对调用方是透明的(所以说RPC-client中的服务连接池是很重要的基础组件)。
【服务层>缓存层】的高可用
【服务层】到【缓存层】的高可用,是通过缓存数据的冗余来实现的。
缓存层的数据冗余又有几种方式:第一种是利用客户端的封装,service对cache进行双读或者双写。
缓存层也可以通过支持主从同步的缓存集群来解决缓存层的高可用问题。
以redis为例,redis天然支持主从同步,redis官方也有sentinel哨兵机制,来做redis的存活性检测。
自动故障转移:当redis主挂了的时候,sentinel能够探测到,会通知调用方访问新的redis,整个过程由sentinel和redis集群配合完成,对调用方是透明的。
说完缓存的高可用,这里要多说一句,业务对缓存并不一定有“高可用”要求,更多的对缓存的使用场景,是用来“加速数据访问”:把一部分数据放到缓存里,如果缓存挂了或者缓存没有命中,是可以去后端的数据库中再取数据的。
这类允许“cache miss”的业务场景,缓存架构的建议是:
将kv缓存封装成服务集群,上游设置一个代理(代理可以用集群冗余的方式保证高可用),代理的后端根据缓存访问的key水平切分成若干个实例,每个实例的访问并不做高可用。
缓存实例挂了屏蔽:当有水平切分的实例挂掉时,代理层直接返回cache miss,此时缓存挂掉对调用方也是透明的。key水平切分实例减少,不建议做re-hash,这样容易引发缓存数据的不一致。
【服务层>数据库层】的高可用
大部分互联网技术,数据库层都用了“主从同步,读写分离”架构,所以数据库层的高可用,又分为“读库高可用”与“写库高可用”两类。
【服务层>数据库层“读”】的高可用
【服务层】到【数据库读】的高可用,是通过读库的冗余来实现的。
既然冗余了读库,一般来说就至少有2个从库,“数据库连接池”会建立与读库多个连接,每次请求会路由到这些读库。
自动故障转移:当读库挂了的时候,db-connection-pool能够探测到,会自动的进行故障转移,将流量自动迁移到其他的读库,整个过程由连接池自动完成,对调用方是透明的(所以说DAO中的数据库连接池是很重要的基础组件)。
【服务层>数据库层“写”】的高可用
【服务层】到【数据库写】的高可用,是通过写库的冗余来实现的。
以mysql为例,可以设置两个mysql双主同步,一台对线上提供服务,另一台冗余以保证高可用,常见的实践是keepalived存活探测,相同virtual IP提供服务。
自动故障转移:当写库挂了的时候,keepalived能够探测到,会自动的进行故障转移,将流量自动迁移到shadow-db-master,由于使用的是相同的virtual IP,这个切换过程对调用方是透明的。
总结
高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。
方法论上,高可用是通过冗余+自动故障转移来实现的。
整个互联网分层系统架构的高可用,又是通过每一层的冗余+自动故障转移来综合实现的,具体的:
(1)【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
(2)【反向代理层】到【站点层】的高可用,是通过站点层的冗余实现的,常见实践是nginx与web-server之间的存活性探测与自动故障转移
(3)【站点层】到【服务层】的高可用,是通过服务层的冗余实现的,常见实践是通过service-connection-pool来保证自动故障转移
(4)【服务层】到【缓存层】的高可用,是通过缓存数据的冗余实现的,常见实践是缓存客户端双读双写,或者利用缓存集群的主从数据同步与sentinel保活与自动故障转移;更多的业务场景,对缓存没有高可用要求,可以使用缓存服务化来对调用方屏蔽底层复杂性
(5)【服务层】到【数据库“读”】的高可用,是通过读库的冗余实现的,常见实践是通过db-connection-pool来保证自动故障转移
(6)【服务层】到【数据库“写”】的高可用,是通过写库的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
今天的文章
什么是高可用,如何实现高可用低可用_nginx高可用分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/80820.html