从0开始学架构_32_31_如何应对接口级的故障
你好,我是华仔。
前几讲我介绍了异地多活方案,它主要用来应对系统级的故障。
比方说机器宕机啊,机房故障啊,还有网络故障啊等等。
不过这些问题虽然影响很大,但是发生的概率比较小。
在实际业务运行的过程中,还有另外一种故障例,然影响可能没有那么大,但是发生的概率比较高。
这就是今天我要跟你聊的接口级的故障。
接口级故障的典型表现就是系统并没有宕机,网络也没有中断,但业务却出现问题了。
例如业务响应缓慢,大量访问超时和大量访问出现异常背后的原因是什么呢?主要就是系统压力太大,负载太高,导致没办法快速处理业务请求,然后引起更多的后续问题。
最常见的情况就是数据库慢查询,把数据库的服务器资源都耗完了,导致读写超时。
业务读写数据库的时候,要么无法连接数据库,要么超时。
结果用户看到的现象就是访问很慢,一会儿访问抛出异常,一会儿访问又是正常结果。
如果我们进一步探究,那么导致接口及故障的原因可以分为两大类。
第一类是内部原因,包括程序bug导致死循环某个接口导致数据库慢查询、程序逻辑不完善,导致耗尽内存等。
第二类是外部原因,包括黑客攻击、促销或者抢购,引入了超出平时几倍甚至几十倍的用户。
第三方系统大量请求第三方系统响应缓慢等解决接口及故障的核心思想呢和异地多活基本类似,都是优先保证核心业务和优先保证。
绝大部分用户常见的方法有四种,降级、熔断、限流和排队。
下面我会一一讲解。
首先是降级,降级是指系统将某些业务或者接口的功能降低,可以使只提供部分功能,也可以使完全停掉所有功能。
例如,论坛可以降级,为只能看帖子,不能发帖子,也可以降级。
为只能看帖子和评论,不能发评论。
而app的日志上传接口,而app的日志上传接口可以完全停掉一段时间。
这段时间内,app都不能上传日志,降级的核心思想就是丢车保帅,优先保证核心业务。
对于论坛来说,百分之九十的流量是看帖子,那我们就优先保证看帖的功能。
对于一个app来说,日志上传接口只是一个辅助的功能,故障时完全可以停掉。
常见的实现降级的方式有两种,第一种是系统后门降级,简单来说就是预留后门用于降级操作,例如系统提供一个降级URL.当访问这个URL的时候,就相当于执行降级指令,具体的指令通过UL的参数传入就行了。
这种方案有一定的安全隐患。
例如,我们会在URL中加入安全措施,比如密码系统、后门降级的方式实现成本低。
但主要缺点是,如果服务器数量多,需要一台一台去操作,效率就比较低,有点浪费时间,不太适合像故障处理这种需要争分夺秒的场景。
为了解决系统后门降级方式的缺点,我们可以将降级操作独立到一个单独的系统中,实现复杂的权限管理、批量操作等功能。
这就是第二种方式独立降级系统。
它的基本架构,如文稿中图片所示,说完降级,我们再说熔断。
熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障,导致自己的系统处理能力急剧下降,或者出故障。
熔断和降级这两个概念很容易混淆,单纯看名字好像都是要禁止某个功能,但它们的内涵是不同的。
因为降级的目的是应对系统自身的故障,而熔断的目的则是应对依赖的外部系统故障的情况。
我们假设一个这样的场景,a服务的x功能依赖b服务的某个接口。
当b服务的接口响应很慢的时候,a服务的x功能响应肯定也会被拖慢。
进一步导致a服务的线程都被卡在x功能处理上。
于是a服务的其他功能都会被卡住或者响应非常慢,这时就需要熔断机制了。
A服务不再请求b服务的这个接口。
A服务内部只要发现是请求b服务的这个接口,就立即返回错误,从而避免a服务整个被拖慢甚至拖死。
实现熔断机制有两个关键点,一是需要有一个统一的API调用层,由API调用层来进行采样或者统计。
如果接口调用散落在代码,各处就没法进行统一处理。
二是阈值的设计,例如一分钟内百分之三十的请求响应时间超过一秒就熔断。
这个策略中的一分钟,百分之三十一秒都对最终的熔断效果有影响。
实践当中一般都是先根据分析确定阈值,然后上线观察效果再进行调优。
接下来我们再说限流。
接果说,降级是从系统功能优先级的角度考虑如何应对故障。
那么限流就是从用户访问压力的角度来考虑如何应对故障。
限流值只允许系统能够承受的访问量进来,超出系统访问能力的请求就会被丢弃。
虽然丢弃这个词听起来让人不太舒服,但是保证一部分请求能够正常响应,总比全部请求都不能响应要好得多。
限流一般都是系统内实现的,常见的限流方式可以分为两类,基于请求限流和基于资源限流。
第一类是基于请求限流,它是指从外部访问的请求角度考虑限流。
常见的方式有两种,第一种是限制总量,也就是限制某个指标的累积上限。
常见的是限制当前系统服务的用户总量。
例如,某个直播间限制总用户数上限为一百万,超过一百万后,新的用户无法进入某个抢购活动,商品数量只有一百个限制,参与抢购的用户上限为一万个一万以后的用户直接拒绝。
第二种是限制时间量,也就是限制一段时间内某个指标的上限。
例如,一分钟内只允许一万个用户访问每秒,请求峰值最高为十万。
无论是限制总量还是限制时间量,共同的特点,都是实现简单。
但在实践当中,面临的主要问题是比较难以找到合适的阈值。
例如系统设定了一分钟一万个用户,但实际上六千个用户的时候,系统就扛不住了。
或者达到一分钟一万用户之后,其实系统压力还不大。
但此时已经开始丢弃用户访问了。
即使找到了合适的阈值,基于请求限流,还面临硬件相关的问题。
例如一台三十二核的机器和六十四核的机器处理能力差别很大,阈值是不同的。
可能有的技术人员以为简单,根据硬件指标进行数学运算就可以得出来。
实际上这样是不可行的。
六十四核的机器比三十二核的机器业务处理性能并不是两倍的关系,可能是一点五倍,甚至可能是一点一倍。
为了找到合理的阈值,通常情况下可以采用性能压测来确定阈值,但性能压测也存在覆盖场景有限的问题,可能出现某个性能压测没有覆盖的功能,导致系统压力很大。
另外一种方式是逐步优化,先设定一个阈值,然后上线观察运行情况,发现不合理就调整阈值。
基于上述的分析,根据阈值来限制访问量的方式,更多的适应于业务功能比较简单的系统,例如负载均衡系统、网关系统、抢购系统等。
第二类限流方式是基于资源限流,基于请求限流是从系统外部考虑的。
而基于资源限流则是从系统内部考虑的,也就是找到系统内部影响性能的关键资源对它的使用上限进行限制。
常见的内部资源包括连接数文件、句柄、线程数和请求队列等。
例如采用netty来实现服务器,每个进来的请求都先放入一个队列业务线程,再从队列读取请求进行处理。
队列长度最大值为一万,队列满了就拒绝后面的请求,也可以根据CPU的负载或者占用率进行限流。
当CPU的占用率超过百分之八十的时候,就开始拒绝新的请求和基于请求。
限流相比基于资源,限流更能有效的反映当前系统的压力,但实际设计时也面临两个主要的难点。
如何确定关键资源以及如何确定关键资源的阈值。
通常情况下,这也是一个逐步调优的过程。
设计的时候,先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化。
为了更好的实现前面描述的各种限流方式,通常情况下,我们会基于限流算法来设计方案,常见的限流算法有两大类。
四小类,它们的实现原理和优缺点各不相同,在实际设计的时候,需要根据业务场景来选择。
第一大类是时间窗算法,它会限制一定时间窗口内的请求量或者资源消耗量。
根据实现方式又可以细分为固定时间窗和滑动时间窗。
固定时间窗算法的实现原理是统计固定时间周期内的请求量或者资源消耗量超过限额就会启动限流。
它的优点是实现简单,缺点是存在临界点问题。
例如,上图中的红蓝两点只间隔了短短十秒期间的请求数,却已经达到两百超过了算法规定的限额。
但是,因为这些请求分别来自两个统计窗口,从单个窗口来看,还没有超出限额,所以并不会启动限流。
结果可能导致系统因为压力过大而挂掉。
为了解决临界点问题,滑动时间窗算法应运而生。
它的实现原理是两个统计周期部分重叠,从而避免短时间内的两个统计点分属不同的时间窗的情况。
总体上来看,滑动时间窗的限流效果要比固定时间窗更好,但是时限也会稍微复杂一些。
第二大类是桶算法,用一个虚拟的桶来临时存储一些东西,根据桶里面放的东西又可以细分为漏桶和令排桶。
漏桶算法的实现原理是将请求放入桶业务处理单元,从桶内拿,请求处理桶满则丢弃新的请求。
我们可以看到漏桶算法的三个关键实现点。
第一点,流入速率不固定,可能瞬间流入非常多的请求。
例如零点签到,整点秒杀,第二点匀速流出,这是理解漏桶算法的关键。
也就是说,即使大量请求进入了漏桶,但是从漏桶流出的速度是匀速的速度的最大值就是系统的极限处理速度。
这样就保证了系统在收到海量请求的时候不会被压垮,这是低层的保护措施。
需要注意的是,如果漏桶没有堆积,那么流出速度就等于流入速度,这个时候流出速度就不是匀速的。
第三个点桶满则丢弃请求,这是第二层保护措施。
也就是说,漏桶不是无限容量,而是有限容量。
例如漏桶最多存储一百万个请求桶满了则直接丢弃后面的请求。
漏桶算法的技术本质是总量控制,桶大小是设计关键。
具体的优缺点如下,第一点,突发大量流量时丢弃的请求较少,因为漏桶本身有缓存请求的作用。
第二点,桶大小动态调整比较困难,需要不断的尝试,才能找到符合业务需求的最佳桶大小。
第三点,无法精确控制流出速度,也就是业务的处理速度。
漏桶算法主要适用于瞬时高并发流量的场景,在短短几分钟内涌入大量请求时,为了更好的业务效果和用户体验,即使处理慢一些,也要做到尽量不丢弃用户请求。
令牌。
桶算法和漏桶算法的不同之处在于,桶中放入的不是请求,而是令牌。
这个令牌就是业务处理前需要拿到的许可证。
也就是说,当系统收到一个请求时,先要到令牌桶里面拿令牌,拿到令牌才能进一步处理,拿不到就要丢弃请求。
我们可以看到令排筒算法的三个关键设计点。
第一点有一个处理单元,往桶里面放,令牌放的速率是可以控制的。
第二点,桶里面可以累积一定数量的令牌。
当突发流量过来的时候,因为桶里面有累积的令牌,此时的业务处理速度会超过令牌放入的速度。
第三点,如果令牌不足,即使系统有能力处理,也会丢弃请求令牌桶算法的技术本质是速率控制,令牌产生的速率是设计关键。
具体的优缺点如下,第一点,可以动态调整,处理速率,实现更加灵活。
第二点,突发大量流量的时候可能丢弃很多请求,因为令牌桶不能累积太多令牌。
第三点,实现相对复杂。
令排筒算法主要适用于两种典型的场景,一种是需要控制访问第三方服务的速度,防止把下游压垮。
例如,支付宝需要控制访问银行接口的速率,另一种是需要控制自己的处理速度,防止过载。
例如,压测结果显示,系统最大处理TPS是一百,那么就可以用令排桶来限制最大的处理速度。
这四种限流算法的实现原理我都绘制成了图片。
你可以在文稿当中查看。
刚才介绍漏桶算法的时候,我提到漏桶算法可以应对瞬时高并发流量。
现在介绍令牌桶算法的时候,我又说令牌桶允许突发流量。
你可能会问,这两种说法好像差不多呀,他们到底有什么区别,到底谁更适合做秒杀呢?其实,令排筒的允许突发,实际上只是允许一定程度的突发。
比如系统处理能力是每秒一百TPS,突发到一百二十TPS是可以的。
但如果突发到一千TPS的话,系统大概率就要被压垮了。
所以处理秒杀时高并发流量还是得用漏桶算法令。
排桶的算法原本是用于网络设备控制传输速度的,而且它控制的目的是保证一段时间内的平均传输速度。
之所以说令牌桶适合突发流量,是指在网络传输的时候,可以允许某段时间内超过平均传输速率。
这在网络环境下常见的情况就是网络抖动。
但这个短时间的突发流量并不会导致雪崩效应,网络设备也能够处理的过来。
对应到令牌桶应用到业务处理的场景,就要求即使有突发流量来了系统,自己或者下游系统要真的能够处理的过来,否则令牌桶允许突发流量进来,结果系统或者下游处理不了,那还是会被压垮。
因此,令牌桶在实际设计的时候,桶大小不能像漏桶那样设计的很大,需要根据系统的处理能力来进行仔细的估算。
例如,漏桶算法的桶容量可以设计为一百万,但是一个每秒三十TPS的令牌,桶桶的容量可能只能设计成四十左右。
海外有的银行给移动钱包提供的接口TPS上限是三十,压测到了四十就真的挂了。
四、排队排队实际上是限流的一个变种,限流是直接拒绝用户排队,是让用户等待一段时间。
全世界最有名的排队当属幺二三零六网站的排队排队虽然没有直接拒绝用户,但用户等了很长时间后,进入系统体验并不一定比限流好。
由于排队需要临时缓存,大量的业务请求,单个系统内部无法缓存这么多数据。
一般情况下,排队需要用独立的系统去实现。
例如使用卡卡这类消息队列来缓存用户请求一号店的双十一秒杀排队系统架构以及它的基本实现我都放到了文稿当中。
小结想,今天我为你讲了接口及故障的四种应对方法,分别是降级、熔断、限流和排队。
希望对你有所帮助,这就是今天的全部内容,留一道思考题给你。
如果你来设计一个整点限量秒杀系统,包括登录、抢购、支付等功能。
你会如何设计接口级的故障应对手段?欢迎你把答案写到留言区,和我一起讨论。
相信经过深度思考的回答,也会让你对知识的理解更加深刻。