服务雪崩
微服务化产品线,每一个服务专心于自己的业务逻辑,并对外提供相应的接口,看上去似乎很明了,其实还有很多的东西需要考虑,比如:服务的自动扩充,熔断和限流等,随着业务的扩展,服务的数量也会随之增多,逻辑会更加复杂,一个服务的某个逻辑需要依赖多个其他服务才能完成。一旦一个依赖不能提供服务很可能会产生雪崩效应,最后导致整个服务不可访问。
微服务之间往往采用rpc或者http调用,我们一般都会设置调用超时,失败重试等机制来确保服务的成功执行,但即使如此,如果不考虑服务的熔断和限流,那就是雪崩的源头。
举例:
假设我们有如下调用链:
-
两个访问量比较大的服务A和B,这两个服务分别依赖C和D,C和D服务都依赖E服务。
-
突然有一天,比如双11,特殊节日等,A、B节点的访问突然暴增,但此时A,B节点的服务器还能勉强抗的住。A和B不断的调用C,D处理客户请求和返回需要的数据,而C和D不断得调用E,E的访问一下成倍的增长,直接击垮了服务E,导致服务E不可用了。
-
当E服务不能供服务的时候,C和D的超时和重试机制会被执行。由于新的调用不断的产生,会导致C和D对E服务的调用大量的积压,产生大量的调用等待和重试调用,慢慢会耗尽C和D的资源比如内存或CPU,然后也down掉。
-
A和B服务会重复C和D的操作,资源耗尽,然后down掉,最终整个服务都不可访问。
整个过程如下:
如上图所示,一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。
常见的导致雪崩的情况有以下几种:
-
服务提供者不可用
-
硬件故障:硬件损坏造成服务器主机宕机,网络硬件故障造成服务提供者的不可访问
-
程序的bug:例如各种异常导致线程挂掉
-
缓存击穿:缓存击穿一般发生在应用重启,此时所有缓存被清空,以及短时间内大量缓存失效,进而导致大量请求直接打到后段,使数据系统超负荷运行,引起服务不可用
-
用户大量请求:在秒杀和大促开始前,如果准备不充分,用户在短时间内发起大量数据请求导致服务阻塞不可用
-
doc等病毒攻击导致服务资源耗尽
-
-
重试加大流量
-
用户重试:服务端不可用,由于用户忍受不了界面上长时间的等待,不断刷新页面请求数据加大流量。
-
代码逻辑重试:服务调用端一次调用失败后可能有多次重试逻辑导致流量加大
-
-
服务调用者不可用
-
服务上游很多同步等待逻辑(例如RPC同步模式),可能导致服务上游产生大量的等待线程占用系统资源,线程可能被耗尽,进而导致服务上游也不能对外提供服务,服务雪崩效应也就产生了
-
防止服务雪崩的措施
手段
熔断、隔离、限流、降级
原则
出错前预防:限流、降级(主动降级)
出错后修正:熔断、隔离、降级(被动降级)
-
隔离
-
资源隔离
服务中最重要的资源往往是线程,所以线程的隔离非常重要
这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。
优缺点:有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
-
系统隔离
系统之间本来就是隔离的,为啥还要讲系统隔离呢?
这里的系统隔离是指系统间接口调用是使用同步接口还是异步接口的问题。比如,用户通过订单系统取消订单,而取消订单时需要通知仓库系统创建入库单。这时,如果仓库系统出现了故障,出现的结果是,用户怎么都取消不了订单,这是不能忍受的。这就是同步调用接口的后果
但是,如果这时使用异步方法,实现两个系统间的隔离,就能让错误只体现在出错的系统中,使用户体验最优化
-
-
限流
为什么要限流:
-
防作弊(比如使用脚本秒杀商品)
-
防DOS攻击(Denial Of Service)以及升级版的DDOS
-
防止资源被过度使用(如连接池、线程池)
限制客户端的调用来达到限流的做法是很常见的,比如,我们限制每秒最大处理200个请求,超过个数量直接拒绝请求。
常见限流算法:
-
计数器算法
-
固定窗口计数器算法
规定单位时间处理的请求数量。比如我们规定我们的一个接口一分钟只能访问100次的话。使用固定窗口计数器算法的话可以这样实现:给定一个变量counter来记录处理的请求数量,当1分钟之内处理一个请求之后counter+1,1分钟之内的如果counter=100的话,后续的请求就会被全部拒绝。等到 1分钟结束后,将counter回归成0,重新开始计数
缺点:这种限流算法无法保证限流速率,因而无法保证突然激增的流量。假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。
-
滑动窗口计数器算法
它把时间以一定比例分片。例如我们的借口限流每分钟处理600个请求,我们可以把 1 分钟分为60个窗口。每隔1秒移动一次,每个窗口一秒只能处理 不大于 600(请求数)/60(窗口数) 的请求, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
优点:这样使得每个时间段的速率保持平滑,也可以避免了固定窗口计数器算法中恶意用户的情况
-
-
漏桶算法
我们可以把发请求的动作比作成注水到桶中,我们处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
实现:如果想要实现这个算法的话也很简单,准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了。
-
令牌桶算法
令牌桶算法也比较简单。和漏桶算法算法一样,我们的主角还是桶。不过现在桶里装的是令牌了,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃(删除)。我们根据限流大小,按照一定的速率往桶里添加令牌。
-
-
熔断
这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
是否启用熔断的标准可以是失败的次数,也可以是失败请求占总请求的比例。
以hystrix为例,熔断器设计的三个主要模块
-
熔断判断算法:代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态
-
熔断恢复算法:当熔断器熔断后,会开启一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。在半熔断状态下,允许对应用程序的一定数量的请求可以去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态(并且将错误计数器重置);如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开方式。
-
熔断报警:记录熔断状态,通知熔断消息
-
-
降级
什么是服务降级?
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
降级的目的:
使非必要服务不影响主要服务
-
系统故障-要降级,避免服务雪崩。如系统中运营位出问题时,可以暂时并降级,待问题解决后再重新提供服务。
-
高并发情况-次要服务要降级,为主要服务提供最大的支持。如,双11的时候,淘宝可以把评论服务降级
自动降级:
-
超时降级 —— 主要配置好超时时间和超时重试次数和机制,并使用异步机制探测恢复情况
-
失败次数降级 —— 主要是一些不稳定的API,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
-
故障降级 —— 如要调用的远程服务挂掉了(网络故障、DNS故障、HTTP服务返回错误的状态码和RPC服务抛出异常),则可以直接降级
-
限流降级 —— 当触发了限流超额时,可以使用暂时屏蔽的方式来进行短暂的屏蔽
处理策略:
-
页面降级:某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级
-
页面片段降级:比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级;
-
页面异步请求降级:比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;
-
服务功能降级:比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;
-
读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;
-
写降级:比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
-