有的同学虽然经常听说"系统可用性"、"系统可靠性"、"系统稳定性"这几个词,但却又傻傻分不清其中差别,我先来解释一下。
系统可用性(Availability) :高可用的系统,故障时间少,止损快,在任何给定的时刻都可以工作。一般公司在系统可用性的要求在99.9%------99.99%之间,即:宕机时长在50分钟------500分钟之间。
系统可靠性(Reliability) :高可靠的系统,故障次数少,频率低,在较长的时间内无故障地持续运行。
系统稳定性(Stability): 在系统可靠性和可用性之上,即降低故障频次和提升止损速度的情况下,要求系统的性能稳定,不要时快时慢。
换言之,不但要求系统尽可能地随时可以提供服务,并且希望系统提供有质量保障的服务。
为了便于大家理解"系统可用性"和"系统可靠性"的区别,举个例子:
-
如果系统在每小时崩溃一毫秒,它的可用性就超过99.9999%,但它还是高度不可靠的。
-
如果系统从来不崩溃,但每年的圣诞节前后停机两周,它是高度可靠的,但是系统的可用性只有96%。
我浅显地认为,问高可用相关问题的面试官,要比问高并发和高性能问题的面试官经验丰富一些。
至于原因,我引用朴树《平凡之路》中的歌词片段,来娱乐一下:
我曾经跨过山和大海(嗷嗷优化性能),也穿过人山人海(老想着提升并发量),我曾经拥有着的一切(绩效得S),转眼都飘散如烟(出了大故障),我曾经失落失望,失掉所有方向(被毕业了),直到看见平凡,才是唯一的答案(好好搞系统可用性)。
其实,真的是经历过系统出现大故障的切肤之痛,才真的会在任何时候,都把系统的可用性放在第一位。
下面我们来分析一下,构建一个高可用系统有哪些策略,其大类可以分为三类:减少故障次数、降低故障时长、
缩小故障范围,然后每大类中又有各自细分小类。
整体如下:
减少故障次数
限流
系统外部的请求流量是不受控的,有可能会在某个时间点,由于某个外部因素,导致了流量激增。
而限流的目的,则是要保护系统不被超出其处理能力的请求冲垮,通过拒绝请求的方式,保证系统的可用性。
在限流工具上,无论是Guava的RateLimiter,还是SpringCloud Alibaba的Sentinel,功能都非常完善好用。
但是,限流的关键点,从来不在工具上,而在于其阈值设置 和多级布控上。
(1)阈值设置
- 如果设置限流的阈值高于系统的承载量,那限流动作就等于形同虚设了。
- 如果设置限流的阈值过低,则拦住了一部分本可以正常处理的业务请求,或造成了服务器硬件资源的浪费。
那么问题来了,如何才能探查到系统合理的限流阈值呢?
我认为最好的方案是,在系统的业务低峰期,以真实流量回放、并递增加压的方式(1倍、1.5倍、2倍、2.5倍、3倍等)进行压测,探查系统所能承载的最大容量,然后将限流阈值设置为其峰值容量的50%------70%。
之所以设置50%------70%,就是为一些意外情况留有buffer,比如:两天后代码迭代上线,业务逻辑变复杂了,消耗了更多的硬件资源,或是压测所得到的峰值容量有些偏差,等等。
当然,这里所说的压测,如果是读写混压的话,还涉及到影子库影子表和路由策略的一些知识点,在这里就不进行展开了。
(2)多级布控
我们对A系统进行压测,得出来系统的峰值容量为1000 QPS,按照其50%------70%设置系统整体的限流阈值,这样就完全高枕无忧吗?
有些系统是可以的,但有些系统未必。
假如:
我们把A系统中的限流阈值设置为600 QPS,正常情况下,系统中有90%的接口耗时在20ms以下,另外10%的接口耗时则高达2000ms以上。
但忽然有一天,耗时2000ms以上接口比例,从10%增加到了50%,而耗时在20ms以下的接口比例,则从90%减少到了50%。
这种情况下,系统还是会出问题的。
除非,我们在系统整体限流的情况下,对这几个耗时奇高的接口,再case by case地单独设置限流阈值。即:总分多级布控。
除此场景外,如果系统为几个不同的外部公司,或者公司内不同的几个业务线同时提供服务,也需要根据其请求来源进行总分多级布控。
防刷
防刷,通过限制同一时间内单一用户(或IP)对特定接口的访问次数,通过拒绝请求的方式,保证系统的可用性。
如果说限流,所限制的是正常的用户业务请求的话,那防刷策略,则更多的是防止恶意请求(DDoS攻击)、不正常请求(工具抢票),以及代码实现问题(接口内重复调用、循环调用)。
防刷策略的话,我越在上层进行拦截处理越好,优先在WAF(Web应用防火墙)进行拦截,其次在Nginx进行拦截,最后在应用服务器进行拦截。
常见解决方案:
(1)通过WAF进行IP围栏和DDoS速率限制。
(2)通过Nginx的limit_req_zone参数,限制单个IP的请求处理速率。
(3)通过用户名 + 方法名作为Redis Key,并把过期时间设置为间隔阈值,限制用户名的请求处理速率。
不过,这种方式适合于业务流程型的大接口(如:下单、约课、查询商品列表),而非By ID类的小接口,同时也需要跟上游团队约定规范,以免误杀正常业务使用。
超时设置
一般情况下,系统对于下游服务的依赖,分为强弱依赖。
- 强依赖:假定服务A依赖于服务B,服务B出现故障不可用时,服务A也不可用。
- 弱依赖:假定服务A依赖于服务B,服务B出现故障不可用时,服务A虽然受到了些许影响,但是仍然可用。
假设如下场景:
当服务A请求下游的强依赖服务B的某接口,该接口TP50的响应时间为10ms,TP99的响应时间为100ms,但TP999的响应时间为5000ms,相当于是处理TP50的500个请求的时间总和。
解释一下:TP99 = 50ms,标识这段时间99%的请求都在50毫秒以内,TP50和TP999也是相同计算策略。
为了防止高并发下,TP99以上,甚至是TP999这种长尾请求对系统造成的影响,都会给下游接口调用设置一个合理的超时时间。
此举是为了牺牲零星接口的处理结果,保证系统中的绝大绝大多数请求不被其所影响,确保业务正常运行。
一般接口的超时时间,会设置在TP99和TP999之间,比如:TP99的响应时间为100ms,则超时时间可以设置为200ms。
系统巡检
系统巡检一般是在应用在代码上线后,或是系统业务高峰期以前进行的,旨在提前发现并处理系统中的潜在问题。
业务高峰期以前进行,适合于业务波峰和波谷比较明显的情况。
举个例子:在线教育类业务,周中的晚上(19点 ------ 21点)为业务高峰期,那么可以选择16点以后禁止发布上线,17点进行巡检,一旦发现系统问题,还有两个小时的时间进行处理解决。
巡检内容包括:
(1)应用、数据库、中间件服务器的硬件指标,比如:负载、CPU、磁盘、网络、内存、JVM等。
(2)系统QPS、TPS、接口响应时间、错误率等。
(3)是否有新增的慢查SQL,以及SQL执行时间和次数等,这点尤为关键。
故障复盘
现在市面上故障复盘的方法论很多,比如:5 WHYS分析法、5W2H分析法、黄金三问分析法等,其本质都是围绕故障本身去进行深挖,用追根究底的精神去发掘问题的本质,而不是仅仅停留在"开发的时候没有想到"、"测试的时候没有覆盖到"、"巡检的时候遗漏了"等层面。
接下来,根据"重要紧急"、"重要不紧急"两个维度,制定短期和中期TODO,务必明确执行人以及完成时间,并持续地监督跟进,直到所有的TODO全部完成。
另外,TODO必须是可落地的,而不是"下次开发的时候多思考"、"下次测试的时候多重视"、"下次巡检的时候多注意"之类的口号流。
另外,从"减少故障次数"这个角度来说,故障复盘不但是通过流程规范和技术策略,保证在以后的开发迭代中,系统不再引入增量的同类问题,也是一种由点及面地去清理现有系统中的存量问题。
结语
本文已经将"减少故障次数"的内容大致讲了一下,后续的文章中再讲讲"降低故障时长"和"缩小故障范围"。