从公众号转载,关注微信公众号掌握更多技术动态
---------------------------------------------------------------
????不要尝试着去避免故障,而是要把处理故障的代码当成正常的功能做在架构里写在代码里。
????高可用是一种面向风险设计,使系统具备控制风险,提供更高的可用性的能力。网站页面能完整呈现在最终用户面前,需要经历很多环节,任何一个环节出现了问题出了问题,都可能导致网站页面不可访问,所以在架构设计的时候很可能遗漏某个环节的高可用设计,这也就让那个高可用变成了最为复杂的架构设计点。
????架构的高可用,就意味着实现系统中所有元素、连接的高可用。在用户眼里,业务永远是正常(或基本正常)对外提供服务的。在物理实例层面主要表现为冗余和集群设计,在代码逻辑层面,方法则多种多样。当资源和精力不足以实现全链路高可用时,提供“降级服务”和“熔断服务”,优先保证高可用,其次保证高可靠。企业里面有些核心服务是不能降级的,对于这类服务,就一定要通过研发流程管理,确保服务的高可用、高可靠。
1.高可用简介
(1)为什么要做高可用
高可用的大前提:所有事物都不是100%可靠的
从人的层面:人都是有可能犯错的。
从软件层面:软件都是有可能有BUG的。
从硬件层面:硬件都是有可能会坏的。
不可抗力:地震,水灾,火灾
(2)高可用影响计算公式
E(r)=nPRT
风险:指未来会发生危害的一种可能性,但实际未发生,记为r。
风险概率:指一个风险变故障的概率。用它来表示风险触发为故障的难易程度,记为P(r)。
故障影响范围:指在单位时间内,一个故障造成的危害影响,记为R(r)。
故障影响时长:指一个故障持续的时间,记为T(r)。
风险期望:指每个风险变故障的概率乘以每个风险变故障后的故障影响面的总和。这里用风险期望来表示风险的潜在危害程度,记为E(r)。
①减少风险数量,n
从源头远离风险,做到与风险载体无连接,无关系;那么该风险概率就是0,也不关心该风险发生后的故障影响面是大是小,完全不关心。
例如:重大节日活动,施行全站封网,变更的数量就会得到一个明显的下降,就是典型的减少风险数量。
例如:系统A完全不依赖Oracle,那系统A就不用关心Oracle的任何风险,哪怕美国总统突然紧急宣布Oracle立即立刻禁止在中国使用,系统A也无所谓。
②降低风险变故障的概率(即:增加风险变故障的难度),P
把风险当成一个对象看待,给它层层设卡,增加风险变故障的门槛和难度,不要再让“不小心多了一个空格或字符,系统就挂了”这种惨案轻易出现。
例如:人员B要对系统C进行变更,可以对人员B增加变更认证考试,对变更内容要求线下(或仿真)测试,对变更内容进行CR,系统C提供变更效果预览能力(类似监控模式或试运行),万一人员B想恶意变更搞破坏,还可以增加非同人复核,系统C可以增加防错设计进行保护等等。
③减小故障影响范围,R
以大拆小,将一个整体拆分成N个小的个体,每个个体之间进行相互隔离,单个个体出问题仅影响单个个体,实现小而美。例如:分布式架构就是这个的典范,集中式一损俱损,分布式一损即N分之一损。
④缩短故障影响时长,T
故障影响时长由故障发现时间和故障止血时间决定,所以要早发现早止血。发现方式分为:事前的预警,事后的告警。尽可能朝事前预警去做,给止血争取时间甚至将风险扼杀在摇篮中。
止血方式分为:切换,回滚,扩容,降级 or 限流,BUG修复等。故障出现时第一优先原则为快速止血(如切换、回滚、扩容),严禁去定位根因;当无法快速止血时以少流血为第二优先原则,如降级、限流。
止血效率:自动 vs 人工 ;一键化 vs 多步操作。尽可能用自动化去代替人工操作,若人工操作时尽量实现一键化,提升止血速度。
例如:对于容量水位,可以在警戒线之前划一条预警线,提前预警,从容应对。
例如:分布式应用集群,任何一台应用服务器有问题时,负载均衡会通过心跳检查自动把有问题的应用服务器剔除,将请求转发给其他(热)备份冗余的服务器上。
(3)可用性风险
计算系统变化:运行变慢,运行错误。系统运行所依赖的服务器资源(如CPU,MEM,IO等),应用资源(RPC线程数,DB连接数等),业务资源(业务ID满了,余额不足,业务额度不够等)的负载等都会影响系统运行的风险期望。
存储系统变化:运行变慢,运行错误,数据错误。系统运行所依赖的服务器资源(如CPU,MEM,IO等),存储资源(并发数等),数据资源(单库容量,单表容量等)的负载和数据一致性等都会影响存储系统运行的风险期望。
程序、人的变化:变更出错。线上系统最容易出现问题的原因就是程序的变更,这里程序的变更包括需求的变更和bug的修复。bug修复有20%——50%引入新的bug,因为往往修复局部问题的时候很难考虑到修复该问题是否会对其它功能产生影响,所以回归测试很重要。这也是为什么“变更三板斧”这么出名的原因,“变更三板斧”正确的排序应该是“可灰度,可监控,可应急”;可灰度代表的是R,可监控和可应急代表的是T。
硬件变化:损坏。硬件的数量,质量,使用年限,保养等都会影响硬件的风险期望,硬件损坏会影响上层软件系统不可用。
上游变化:请求变大。请求分为3个维度:(由无数API汇集而成的)网络流量,(由无数KEY请求组成的)API,KEY。网络流量过大会造成网络堵塞,影响网络通道中的所有网络流量请求。API请求过大会造成对应服务集群过载,影响整个服务机器上的所有API请求,甚至往外传播。以大促保障的时候,不仅仅是关注核心API的容量保障,还需要考虑网络流量和热点KEY。
下游变化:响应变慢,响应错误。下游服务的数量,服务等级,服务可用率等影响下游服务的风险期望。下游响应变慢可能会拖慢上游,下游响应错误可能会影响上游运行结果。
时间变化:时间到期。时间到期往往被人忽视,但它往往具有突然性和全局破坏性,一旦时间到期触发故障会导致非常被动,所以要提前识别,尽早预警,如:秘钥到期,证书到期,费用到期,跨时区,跨年,跨月,跨日等。
2.网站可用性度量
网站不可用也被称为网站故障,业界通常用多少个9来衡量网站的可用性,如qq的可用性是4个9,即qq服务为99.99%可用,这意味QQ服务要保证其在所运行时间中,只有0.01%的时间不可用。
????对于大多数网站而言,2个9是基本可用,3个9是较高可用,4个9是具有自动恢复能力的高可用,5个9是级高可用,所以对于大多数网站达到2个9也就基本可以。
????到达五个九之后,故障就不能靠人力恢复了。想象一下,从故障发生到你接收报警,再到你 打开电脑登录服务器处理问题,时间可能早就过了十分钟了。所以这个级别的可用性考察的 是系统的容灾和自动恢复的能力,让机器来处理故障,才会让可用性指标提升一个档次。
(1)可用性测量
①探针模拟
从客户端侧,模拟用户的调用行为。
优点:数据真实(客户端角度)
缺点:数据不全面(单一客户数据)
②服务端采集
从服务端侧,直接分析日志和数据。
优点:覆盖所有调用数据。
缺点:缺失客户端链路数据。
对可用性数据要求较高的系统,也可以同时运用上述两种方式,建议结合你的业务场景综合评估选择。
(2)可用原则
应该关注 RT 的数据分布(如:p50/p99/p999 分位点),而不是平均值(mean) —— 平均值并没有太大意义,更应该去关注你那 1%、0.1% 用户的准确感受。不要尝试承诺和优化可用性到 100% —— 一方面是无法实现,存在太多客观不可控因素;另一方面也没有意义,客户几乎关注不到 0.001% 的可用性差别。
3.系统故障点
首客户端在远程发起请求,经过接入系统处理后,请求被转发给应用系统;应用系统调 用服务完成具体的功能;在这个过程中,应用和服务还会访问各种资源,比如数据库和缓 存。这里用红色部分,标识出了整个处理过程中可能出现的故障点
资源不可用,包括网络和服务器出故障,网络出故障表明节点连接不上,服务器出故障 表明该节点本身不能正常工作。
资源不足,常规的流量进来,节点能正常工作,但在高并发的情况下,节点无法正常工 作,对外表现为响应超时。
节点的功能有问题,这个主要体现在我们开发的代码上,比如它的内部业务逻辑有问 题,或者是接口不兼容导致客户端调用出了问题;另外有些不够成熟的中间件,有时也 会有功能性问题。
4.保证系统可用性根本策略
冗余备份。任何程序、任何数据,都至少要有一个备份,也就是说程序至少要部署到两台服务器,数据至少要备份到另一台服务器上。此外,稍有规模的互联网企业都会建设多个数据中心,数据中心之间互相进行备份,用户请求可能会被分发到任何一个数据中心,即所谓的异地多活,在遭遇地域性的重大故障和自然灾害的时候,依然保证应用的高可用。
失效转移。当要访问的程序或者数据无法访问时,需要将访问请求转移到备份的程序或者数据所在的服务器上,这也就是失效转移。失效转移最重要的就是失效的鉴定。
限流和降级。可以拒绝部分请求,即进行限流;也可以关闭部分功能,降低资源消耗,即进行降级。
5.高可用设计方向
架构阶段:架构阶段主要考虑系统的可扩展性和容错性,要避免系统出现单点问题。例 如多机房单元化部署,即使某个城市的某个机房出现整体故障,仍然不会影响整体网站 的运转。
编码阶段:编码最重要的是保证代码的健壮性,例如涉及远程调用问题时,要设置合理 的超时退出机制,防止被其他系统拖垮,也要对调用的返回结果集有预期,防止返回的 结果超出程序处理范围,最常见的做法就是对错误异常进行捕获,对无法预料的错误要 有默认处理结果。
测试阶段:测试主要是保证测试用例的覆盖度,保证最坏情况发生时,也有相应的 处理流程。
发布阶段:发布时也有一些地方需要注意,因为发布时最容易出现错误,因此要有紧急 的回滚机制。并且发布过程成一定要保证无流量进入,否则和宕机没有任何区别。
运行阶段:运行时是系统的常态,系统大部分时间都会处于运行态,运行态最重要的是 对系统的监控要准确及时,发现问题能够准确报警并且报警数据要准确详细,以便于排 查问题。
故障发生:故障发生时首先最重要的就是及时止损,例如由于程序问题导致商品价格错 误,那就要及时下架商品或者关闭购买链接,防止造成重大资产损失。然后就是要能够 及时恢复服务,并定位原因解决问题。
1.FMEA简介
????FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等,专栏采用“故障模式与影响分析”,因为这个中文翻译更加符合可用性的语境。FMEA 是一种在各行各业都有广泛应用的可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响。FMEA 并不能指导我们如何做架构设计,而是当我们设计出一个架构后,再使用 FMEA 对这个架构进行分析,看看架构是否还存在某些可用性的隐患。
在架构设计领域,FMEA 的具体分析方法是:
给出初始的架构设计图。
假设架构中某个部件发生故障。
分析此故障对系统功能造成的影响。
根据分析结果,判断架构是否需要进行优化。
FMEA 分析的方法其实很简单,就是一个 FMEA 分析表,常见的 FMEA 分析表格包含下面部分。
(1)功能点
当前的 FMEA 分析涉及的功能点,注意这里的“功能点”指的是从用户角度来看的,而不是从系统各个模块功能点划分来看的。例如,对于一个用户管理系统,使用 FMEA 分析时 “登录”“注册”才是功能点,而用户管理系统中的数据库存储功能、Redis 缓存功能不能作为 FMEA 分析的功能点。
(2)故障模式
故障模式指的是系统会出现什么样的故障,包括故障点和故障形式。需要特别注意的是,这里的故障模式并不需要给出真正的故障原因,我们只需要假设出现某种故障现象即可,例如 MySQL 响应时间达到 3 秒。造成 MySQL 响应时间达到 3 秒可能的原因很多:磁盘坏道、慢查询、服务器到 MySQL 的连接网络故障、MySQL bug 等,我们并不需要在故障模式中一一列出来,而是在后面的“故障原因”一节中列出来。因为在实际应用过程中,不管哪种原因,只要现象是一样的,对业务的影响就是一样的。
此外,故障模式的描述要尽量精确,多使用量化描述,避免使用泛化的描述。例如,推荐使用“MySQL 响应时间达到 3 秒”,而不是“MySQL 响应慢”。
(3)故障影响
当发生故障模式中描述的故障时,功能点具体会受到什么影响。常见的影响有:功能点偶尔不可用、功能点完全不可用、部分用户功能点不可用、功能点响应缓慢、功能点出错等。
故障影响也需要尽量准确描述。例如,推荐使用“20% 的用户无法登录”,而不是“大部分用户无法登录”。要注意这里的数字不需要完全精确,比如 21.25% 这样的数据其实是没有必要的,我们只需要预估影响是 20% 还是 40%。
(4)严重程度
严重程度指站在业务的角度故障的影响程度,一般分为“致命 / 高 / 中 / 低 / 无”五个档次。严重程度按照这个公式进行评估:严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度。同样以用户管理系统为例:登录功能比修改用户资料要重要得多,80% 的用户比 20% 的用户范围更大,完全无法登录比登录缓慢要更严重。因此我们可以得出如下故障模式的严重程度:
致命:超过 70% 用户无法登录。
高:超过 30% 的用户无法登录。
中:所有用户登录时间超过 5 秒。
低:10% 的用户登录时间超过 5 秒。
中:所有用户都无法修改资料。
低:20% 的用户无法修改头像。
(5)故障原因
“故障模式”中只描述了故障的现象,并没有单独列出故障原因。主要原因在于不管什么故障原因,故障现象相同,对功能点的影响就相同。那为何这里还要单独将故障原因列出来呢?主要原因有这几个:
①不同的故障原因发生概率不相同
例如,导致 MySQL 查询响应慢的原因可能是 MySQL bug,也可能是没有索引。很明显“MySQL bug”的概率要远远低于“没有索引”;而不同的概率又会影响我们具体如何应对这个故障。
②不同的故障原因检测手段不一样
例如,磁盘坏道导致 MySQL 响应慢,那我们需要增加机器的磁盘坏道检查,这个检查很可能不是当前系统本身去做,而是另外运维专门的系统;如果是慢查询导致 MySQL 慢,那我们只需要配置 MySQL 的慢查询日志即可。
③不同的故障原因的处理措施不一样
例如,如果是 MySQL bug,我们的应对措施只能是升级 MySQL 版本;如果是没有索引,我们的应对措施就是增加索引。
(6)故障概率
这里的概率就是指某个具体故障原因发生的概率。例如,磁盘坏道的概率、MySQL bug 的概率、没有索引的概率。一般分为“高 / 中 / 低”三档即可,具体评估的时候需要有以下几点需要重点关注。
①硬件
硬件随着使用时间推移,故障概率会越来越高。例如,新的硬盘坏道几率很低,但使用了 3 年的硬盘,坏道几率就会高很多。
②开源系统
成熟的开源系统 bug 率低,刚发布的开源系统 bug 率相比会高一些;自己已经有使用经验的开源系统 bug 率会低,刚开始尝试使用的开源系统 bug 率会高。
③自研系统
和开源系统类似,成熟的自研系统故障概率会低,而新开发的系统故障概率会高。
高中低是相对的,只是为了确定优先级以决定后续的资源投入,没有必要绝对量化,因为绝对量化是需要成本的,而且很多时候都没法量化。例如,XX 开源系统是 3 个月故障一次,还是 6 个月才故障一次,是无法评估的。
(7)风险程度
风险程度就是综合严重程度和故障概率来一起判断某个故障的最终等级,风险程度 = 严重程度 × 故障概率。因此可能出现某个故障影响非常严重,但其概率很低,最终来看风险程度就低。“某个机房业务瘫痪”对业务影响是致命的,但如果故障原因是“地震”,那概率就很低。例如,广州的地震概率就很低,5 级以上地震的 20 世纪才 1 次(1940 年);如果故障的原因是“机房空调烧坏”,则概率就比地震高很多了,可能是 2 年 1 次;如果故障的原因是“系统所在机架掉电”,这个概率比机房空调又要高了,可能是 1 年 1 次。同样的故障影响,不同的故障原因有不同的概率,最终得到的风险级别就是不同的。
(8)已有措施
针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等。
①检测告警
最简单的措施就是检测故障,然后告警,系统自己不针对故障进行处理,需要人工干预。
②容错
检测到故障后,系统能够通过备份手段应对。例如,MySQL 主备机,当业务服务器检测到主机无法连接后,自动连接备机读取数据。
③自恢复
检测到故障后,系统能够自己恢复。例如,Hadoop 检测到某台机器故障后,能够将存储在这台机器的副本重新分配到其他机器。当然,这里的恢复主要还是指“业务”上的恢复,一般不太可能将真正的故障恢复。例如,Hadoop 不可能将产生了磁盘坏道的磁盘修复成没有坏道的磁盘。
(9)规避措施
规避措施指为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段。例如:
技术手段:为了避免新引入的 MongoDB 丢失数据,在 MySQL 中冗余一份。
管理手段:为了降低磁盘坏道的概率,强制统一更换服务时间超过 2 年的磁盘。
(10)解决措施
解决措施指为了能够解决问题而做的一些事情,一般都是技术手段。例如:
为了解决密码暴力破解,增加密码重试次数限制。
为了解决拖库导致数据泄露,将数据库中的敏感数据加密保存。
为了解决非法访问,增加白名单控制。
一般来说,如果某个故障既可以采取规避措施,又可以采取解决措施,那么我们会优先选择解决措施,毕竟能解决问题当然是最好的。但很多时候有些问题是系统自己无法解决的,例如磁盘坏道、开源系统 bug,这类故障只能采取规避措施;系统能够自己解决的故障,大部分是和系统本身功能相关的。
(11)后续规划
综合前面的分析,就可以看出哪些故障我们目前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划。这些规划既可以是技术手段,也可以是管理手段;可以是规避措施,也可以是解决措施。同时需要考虑资源的投入情况,优先将风险程度高的系统隐患解决。
例如:
地震导致机房业务中断:这个故障模式就无法解决,只能通过备份中心规避,尽量减少影响;而机柜断电导致机房业务中断:可以通过将业务机器分散在不同机柜来规避。
敏感数据泄露:这个故障模式可以通过数据库加密的技术手段来解决。
MongoDB 断电丢数据:这个故障模式可以通过将数据冗余一份在 MySQL 中,在故障情况下重建数据来规避影响。
2.FMEA 实战
下面以一个简单的样例来模拟一次 FMEA 分析。假设设计一个最简单的用户管理系统,包含登录和注册两个功能,其初始架构是:
????初始架构很简单:MySQL 负责存储,Memcache(以下简称 MC)负责缓存,Server 负责业务处理。我们来看看这个架构通过 FMEA 分析后,能够有什么样的发现,下表是分析的样例(注意,这个样例并不完整,感兴趣的同学可以自行尝试将这个案例补充完整)。
????经过上表的 FMEA 分析,将“后续规划”列的内容汇总一下,最终得到了下面几条需要改进的措施:
MySQL 增加备机。
MC 从单机扩展为集群。
MySQL 双网卡连接。
改进后的架构如下:
????要想让系统能够稳定可用,首先要考虑如何避免问题的发生。比如说可以通过 UPS(不间断电源)来避免服务器断电,可以通过事先增加机器来解决硬件资源不足的问题。
????然后,如果问题真的发生了,就要考虑怎么转移故障(Failover)。比如说可以 通过冗余部署,当一个节点发生故障时,用其它正常的节点来代替问题节点。如果故障无法以正面的方式解决,就要努力降低故障带来的影响。比如说流量太大,可以通过限流,来保证部分用户可以正常使用,或者通过业务降级的手段,关闭一些次要 功能,保证核心功能仍旧可用。最后是要快速恢复系统。要尽快找到问题的原因,然后修复故障节点,使系统恢复到正常状态。
????这里要强调的是,处理线上事故的首要原则是先尽快恢复业务,而不是先定位系统的问 题,再通过解决问题来恢复系统。因为这样做往往比较耗时,这里给出的处理顺序也体现了 这个原则。
1.正面保障
(1)冗余无单点——节点本身的故障
①单节点故障点
网络链路,服务端网络;
DNS;
机房;
机架,同机房的一批机器;
交换机/路由器;
负载均衡;
物理服务器;
业务服务本身;
缓存 / 数据库 / 存储。
②解决方法
首先要保证系统的各个节点在部署时是冗余的,没有单点。比如在接入层中,可以实现负载均衡的双节点部署,这样在一个节点出现问题时,另一个节点可以快速接管,继续提供服务。还有远程网络通信,它会涉及到很多节点,也很容易会出现问题,就可以提供多条通信 线路,比如移动 + 电信线路,当一条线路出现问题时,系统就可以迅速切换到另一条线 路。甚至可以做到机房层面的冗余,通过系统的异地多 IDC 部署,解决自然灾害(如地 震、火灾)导致的系统不可用问题。
在服务逻辑层采用多运营商多IP入口、跨地&同地多机房部署、同机房多机器部署、分布式任务调度等策略。
在数据存储层采用数据库分库分表、数据库主从备集群、KV存储&消息等分布式系统集群多副本等策略。
有分布式处理能力后,需要考虑单个服务器故障后自动探活摘除、服务器增删能不停服自动同步给依赖方等问题,这里就需引入一些分布式中枢控制系统,如服务注册发现系统、配置变更系统等,例如zookeeper是一个经典应用于该场景的一个分布式组件。
(2)水平扩展——节点处理能力的不足
很多时候,系统的不可用都是因为流量引起的:在高并发的情况下,系统往往会整体瘫痪, 完全不可用。由于硬件在物理上存在瓶颈,通过硬件 升级(垂直扩展)一般不可行,需要通过增加机器数量,水平扩展这些节点的处理能 力。对于无状态的计算节点,比如应用层和服务层来说,水平扩展相对容易,直接增加机器 就可以了;而对于有状态的节点,比如数据库,可以通过水平分库做水平扩展,不过这 个需要应用一起配合,做比较大的改造。
提前预测大流量,提前扩容
实现流量变大,自动扩容
(3)分散、均衡、隔离原则
分散原则(核心):鸡蛋不要放一个篮子,分散风险
例如:所有交易数据都放在同一个库同一张表里面,万一这个库挂了,此时影响
均衡原则(核心):均匀分散风险,避免不均衡
最好N份中的每份都是均衡的;避免某个份额过大,否则过大的那份一有问题就影响范围过大了。例如:xx应用集群有1000台,但由于引流组件BUG,导致所有流量引到了其中100台上面,导致负载严重不均衡,最后因负载无法扛着全面崩溃。类似重大故障已经发生了多次。
隔离原则(核心):控制风险不扩散,不放大
每份之间是相互隔离的;避免一份有问题影响其他的也有问题,传播扩散了影响范围。例如:交易数据拆分成10库100表,但是部署在同一台物理机上;万一某张表有一条大SQL把网卡打满了,那10库100表都会受影响。
对于请求量大的客户进行隔离,分散到一个单独的库
2.减少损失
(1)柔性事务——基本可用和数据的最终一致
系统的可用性经常会和数据的一致性相互矛盾。在 CAP 理论中,系统的可用 性、一致性和网络容错性,三个最多只能保证两个,在分布式系统的情况下,只能在 C 和 A 中选一个。在很多业务场景中,系统的可用性比数据的实时一致性更重要,所以在实践中,更多地 使用 BASE 理论来指导系统设计。
资金交易类系统要仔细考虑资损的风险.交易系统对于数据准确性、一致性、资金损失等都是很敏感的,这一块在是否使用缓存、事务一致性考量、数据时延、数据不丢不重、数据精准核对和恢复等需要额外架构设计考量。仔细评估交易以及营销的全链路各个环节,评估其中出现资损的可能性以及做好应对设计,例如增加多层级对账、券总额度控制、异常金额限制和报警等资损防控的考量等。不同层次不同维度不同时间延迟的对账以及预案是一个重要及时感知资损和止血的有效方式。全链路的过程数据要做好尽可能持久化和冗余备份,方便后续核对以及基于过程数据进行数据修复,同时尽量针对特殊数据丢失场景提供快速自动化修复处理预案(如交易消息可选择性回放和基于幂等原则的重新消费)。
(2)系统可降级——损失非核心功能来保证核心功能的可用
能不依赖的,尽可能不依赖,弱依赖原则:一定要依赖的,尽可能弱依赖。
对于稳定性要求很好的关键系统,在成本可接受的情况下,同时维护一套保障主链路可用的备用系统和架构,在核心依赖服务出现问题能做一定时间周期的切换过渡(例如mysql故障,阶段性使用KV数据库等),例如钉钉IM消息核心系统就实现对数据库核心依赖实现一套一定周期的弱依赖备案,在核心依赖数据库故障后也能保障一段时间消息收发可用。
当系统问题无法在短时间内解决时,就要考虑尽快止损,为故障支付尽可能小的代价。具体的解决手段主要有以下这几种。
限流:让部分用户流量进入系统处理,其它流量直接抛弃。
降级:系统抛弃部分不重要的功能,比如不发送短信通知,以此确保核心功能不受影 响。
超时:避免调用端陷入永久阻塞。
熔断:我们不去调用出问题的服务,让系统绕开故障点,就像电路的保险丝一样,自己 熔断,切断通路,避免系统资源大量被占用。比如,用户下单时,如果积分服务出现问 题,我们就先不送积分,后续再补偿。
功能禁用:针对具体的功能,设置好功能开关,让代码根据开关设置,灵活决定是 否执行这部分逻辑。比如商品搜索,在系统繁忙时,我们可以选择不进行复杂的深度搜索。
(3)自愈 Self-healing
自我修复可以帮助恢复应用程序。自愈是指应用程序可以做一些必要的步骤来恢复崩溃状态。在大多数情况下,这样的操作是经由一个外部系统来实现的,它会监控实例的健康,并在它们较长时间处于错误状态的情况下,重新启动应用程序。自愈是非常有用的,但是在某些情况下,不断地重启应用程序会引起麻烦。由于负载过高或者数据库连接超时,你的应用程序不停的重启,会导致无法提供一个正确的健康状态。实现一种为微妙的情况而准备的高级自我修复解决方案,可能会很棘手,比如数据库连接丢失。在这种情况下,你需要为应用程序添加额外的逻辑来处理一些极端情况,并让外部系统知道不需要立即重启实例。
3.运维策略
可灰度:保障及时在小流量情况,发现问题,避免引发大范围故障。因此在做系统任何变更时,要考虑灰度方案,特别是大用户流量系统。灰度方式可能有白名单用户、按用户Id固定划分后不同流量比例、机器分批发布、业务概念相关分组分比例(例如某个行业、某个商品、某类商品)等,灰度周期要和结合系统风险和流量做合适设计,重要系统灰度周期可能持续超过一周或更多。
可监控:在实践中,系统的故障防不胜防,问题的定位和解决也非常的困难,所以,要想全面保障系 统的可用性,最重要的手段就是监控。当在做功能开发的时候,经常会强调功能的可测试性,通过测试来验证这个功能是 否符合预期,而系统可监控,就像业务功能可测试一样重要。通过监控,我们可以实时地了 解系统的当前状态,这样很多时候,业务还没出问题,就可以提前干预,避免事故;而 当系统出现问题时,我们也可以借助监控信息,快速地定位和解决问题。
可回滚:相同的版本可以反复发布,新增功能增加配置开关,当线上出现问题时,可通过关闭功能开关,快速下线最新升级 或部分有问题功能。针对不同出错场景,有配置驱动一些预案,例如降级对某个服务的依赖、提供合适功能维护中公告、切换到备用服务等预案,在特定问题出现时,可以快速做线上止损和恢复。发布功能注意提前考虑出现问题时快速回滚步骤,部分经常发布注意对回滚步骤做演练。
可转移:需要具备故障转移能力
接入层:DNS、VipServer、SLB。
服务层:服务发现 + 健康检查 + 剔除机制。
应用层:无状态设计(Stateless),便于随时和快速切换。
密闭性。所谓密闭性(Hermetic),简单说就是环境的完整性。比如,软件的源代码必须是密闭的,每次通过特定的版本号,检出内容必须是完整的,一致 的且可重复的。编译的时候不需要再去任何第三方额外检出外部依赖的源代码。再比如,从构建过程来说,同样必须确保一致性和可重复性。让两个工程师在两台不同的机 器上基于同一个源代码版本构建同一个产品,构建结果应该是相同的。这意味着它不应该受 构建机器上安装的第三方类库或者其他软件工具所影响。构建过程需要指定版本的构建工 具,包括编译器,同时使用指定版本的依赖库(第三方类库)。编译过程是自包含的,不依 赖于编译环境之外的任何其他服务。
4.团队研发运维流程机制
技术Review:不同体量设计安排经验更加丰富同学Review,架构师、主管、外部架构师的Review、定期系统整体Review等。
代码Code Review:建立规范和标准,通过CR认证合格同学执行code review动作。
单测:不同风险的系统设定尽量高的行覆盖 & 分支覆盖率标准,复杂逻辑类追求100%分支覆盖。
回归测试:持续积累回归用例,在上线前和上线后执行回归动作;上线前线上引流测试也是一种模拟测试方式,端类型系统还可以用monkey工具做随机化测试。
发布机制:设计发布准入和审批流程,确保每次上线发布都是经过精细设计和审核的(记录系统的任何一次发布和变化),上线过程要做到分批、灰度、支持快速回滚、线上分批观察(日志确认)、线上回归等核心动作。建立发布红线等机制,不同系统设计合适发布时段以及发布灰度观察周期。关于配置修改的时机也应该详细考虑,列在发布流程之中。
自动化运行单元测试案例(unit test);
单元测试覆盖率检查(code coverage);
静态代码质量检查(lint);
人工的代码互审(code review);
无感发布:先让服务从注册中心中下掉,但是java进程先不kill掉
团队报警值班响应机制 (报警群、短信、电话):确保报警有合适人员即时响应处理,团队层面可定期做数据性统计通晒,同时建立主管或架构师兜底机制。
定期排查线上隐患:定期做线上走查和错误日志治理、告警治理,确保线上小的隐患机制化发现和修复。例如在钉钉针对企业用户早晚高峰的特点,设计早值班机制,用于高峰期第一时间应急以及每天专人花一定时间走查线上,该机制在钉钉技术团队持续践行多年,有效发现和治理了钉钉各个线上系统的隐患。
用户问题处理机制:Voc日清、周清等。在钉钉也经历Voc周清到日清的持续机制精进。
线上问题复盘机制:天内、周内问题及时复盘,确保针对每个线上问题做系统和团队精进。
代码质量抽查通晒:定期抽查团队同学代码,做评估和通晒,鼓励好的代码,帮助不好代码的改善。
成立稳定性治理专门topic:合适同学每周做好稳定性过程和精进。
定期压测机制:定期机制化执行,核查线上容量情况。
日常演练机制:预案演练,模拟线上故障的不通知的突袭演练提升团队线上问题应对能力。
错误日志要重视.要定期分析线上错误日志,隐患的问题是藏在错误日志中的。我们现在技术团队会有早值班机制,每个方向每天都有一个技术同学走查线上,以发现线上隐患问题为导向,走查监控大盘、错误日志、用户反馈,通过这个例行机制,很好地防微杜渐。
异常一定要消灭:有异常,基本就意味着系统存在风险,一定要消灭异常;与终端用户相关的异常,要以最高优先级处理:即便是 IT 研发,也要以用户为中心。不是所有的异常都要从 Log 中消失,但对于保留下的异常,一定提交管理层进行审批,说明保留原因;理由不够充分的,需要按排期规划并解决。
异常一定要管理:消灭异常是个长期工程,短期要通过管理行为来进行控制;每个异常都要有具体的负责人:没有和具体的负责人一一对应,往往就意味着管理流于形式;异常也应该企业内部形成规范,不可以各个系统不一致。(最好企业内部有一个专门的异常识别及管理的网站)
异常的 ID 和名字;
对异常的描述;
异常出现的代码位置;
负责此异常的研发、测试和产品人员;
异常发生时的代码版本;
当时使用的异常处理程序。
流程机制要和团队同学共创达成一致后,配合建立topic负责人机制,对流程机制执行度和执行效果要做好过程监测和通晒,建立明确数字化标准和衡量机制(例如钉钉技术团队针对线上问题设定1-5-10标准,1分钟响应5分钟内定位10分钟内恢复),同时建立对应奖惩机制。流程机制也要根据系统状态进行精简或精进,确保流程机制可执行性和生命力。要确保在发布过程中,只有指定的人才能执行指定的操作,而不能随随便便跳过必要的环节 进行发布。
5.发布检查列表
(1)容量规划相关
新功能通常会在发布之初带来临时的用量增长,在几天后会趋于平 稳。这种尖峰式的负载或流量分布可能与稳定状态下有显著区别,之前内部的压力测试可能 失效。公众的兴趣是很难预测的,有时甚至需要为预计容量提供 15 倍以上的发布容量。这种情况 下灰度发布会有助于建立大规模发布时的数据依据与信心。
本次发布是否与新闻发布会、广告、博客文章或者其他类型的推广活动有关?
发布过程中以及发布之后预计的流量和增速是多少?
是否已经获取到该服务需要的全部计算资源?
(2)故障模式相关
针对服务进行系统性的故障模式分析可以确保发布时服务的可靠性。在检查列表的这一部分中,我们可以检查每个组件以及每个组件的依赖组件来确定当它们发 生故障时的影响范围
该服务是否能够承受单独物理机故障?单数据中心故障?网络故障?
如何应对无效或者恶意输入,是否有针对拒绝服务攻击(DoS)的保护?
是否已经支持过载保护?
如果某个依赖组件发生故障,该服务是否能够在降级模式下继续工作?
该服务在启动时能否应对某个依赖组件不可用的情况?在运行时能否处理依赖不可用和 自动恢复情况?
(3)客户端行为相关
最常见的客户端滥发请求的行为,是配置更新间隔的设置问题。比 如,一个每 60s 同步一次的新客户端,会比 600s 同步一次的旧客户端造成 10 倍的负载。重试逻辑也有一些常见问题会影响到用户触发的行为,或者客户端自动触发的行为。假设我 们有一个处于过载状态的服务,该服务由于过载,某些请求会处理失败。如果客户端重试这 些失败请求,会对已经过载的服务造成更大负载,于是会造成更多的重试,更多的负载。客
户端这时应该降低重试的频率,一般需要增加指数型增长的重试延迟,同时仔细考虑哪些错 误值得重试。例如,网络错误通常值得重试,但是 4xx 错误(这一般意味着客户端侧请求 有问题)一般不应该重试。
自动请求的同步性往往还会造成惊群效应。例如,某个手机 APP 开发者可能认为夜里 2 点 是下载更新的好时候,因为用户这时可能在睡觉,不会被下载影响。然而,这样的设计会造 成夜里 2 点时有大量请求发往下载服务器,每天晚上都是如此,而其他时间没有任何请 求。这种情况下,每个客户端应该引入一定随机性。
其他的一些周期性过程中也需要引入随机性。回到之前说的那个重试场景下:某个客户端发 送了一个请求,当遇到故障时,1s 之后重试,接下来是 2s、4s 等。没有随机性的话,短 暂的请求峰值可能会造成错误比例升高,这个周期会一直循环。为了避免这种同步性,每个 延迟都需要一定的抖动,也就是加入一定的随机性
客户端在请求失败之后,是否按指数型增加重试延时?
是否在自动请求中实现随机延时抖动?
(4)流程与自动化相关
虽然鼓励自动化,但是对于发布这件事情来说,完全自动化 是灾难性的。为了保障可靠性,我们应该尽量减少发布流程中的单点故障源,包括人在内。这些流程应该在发布之前文档化,确保在工程师还记得各种细节的时候就完全转移到文档 中,这样才能在紧急情况下派上用场。流程文档应该做到能使任何一个团队成员都可以在紧 急事故中处理问题。
是否已将所有需要手动执行的流程文档化?
是否已将构建和发布新版本的流程自动化?
(5)外部依赖相关
有时候某个发布过程依赖于某个不受公司控制的因素。尽早确认这些 因素的存在可以使我们为它们的不确定性做好准备。例如,服务依赖于第三方维护的一个类库,或者另外一个公司提供的服务或者数据。当第三 方提供商出现故障、Bug、系统性的错误、安全问题,或者未预料到的扩展性问题时,尽早 计划可以使我们有办法避免影响到直接用户。
这次发布依赖哪些第三方代码、数据、服务,或者事件?
是否有任何合作伙伴依赖于你的服务?发布时是否需要通知他们?
当我们或者第三方提供商无法在指定截止日期前完成工作时,会发生什么?