目录
一、服务访问失败的原因和应对策略
(一)服务访问失败的4大原因和分类
1硬件失败
2.分布式环境的固有原因
3.服务自身失败
4.服务依赖失败
(二)服务访问的雪崩效应
(三)服务访问失败的应对策略
二、服务容错
1.Failover
2.Failback
3.Failsafe
4.Failfast
5.Forking
6.Broadcast
三、服务隔离
1.线程隔离
2.进程隔离
3.集群隔离
4.机房隔离
5.读写隔离
四、服务限流
1.计数器法
2.滑动窗口法
3.漏洞算法
4.令牌桶算法
五、服务降级
1.服务分级
2.服务熔断
3.服务降级与服务熔断的区别
参考书籍、文献和资料:
一旦出现便是灾难性的,一般分为两类:
例如机房失火、机器损害等不可抗力导致的、发生概率极低的情况;
例如由于日志文件过大导致硬盘无法写入、网络路由无效等可以通过调整硬件状态进行恢复的失败情况。
分布式系统中会由于网络的三态性、异构系统集成等因素导致远程调用发生异常情况,微服务作为分布式系统的延伸这些问题依旧存在并无法完全消除,只能在设计和实现时加以预防,以及在发生时降低其所造成的影响。
由于设计上考虑不周、代码中存在的问题造成的失败,需要深入分析并找到解决问题的方法。
服务依赖失败相比服务自身失败造成的影响更大且难以发现和处理,是我们重点考虑的失败原因,因为依赖失败的扩散会导致服务访问的雪崩效应。
服务雪崩效应是一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用 逐渐放大 的过程.如果所示:
上图中,A为服务提供者,B为A的服务调用者,C和D是B的服务调用者。当A的不可用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成了。
我把服务雪崩的参与者简化为 服务提供者 和 服务调用者, 并将服务雪崩产生的过程分为以下三个阶段来分析形成的原因:
服务提供者不可用
重试加大流量
服务调用者不可用
服务雪崩的每个阶段都可能由不同的原因造成, 比如造成 服务不可用 的原因有:
硬件故障
程序Bug
缓存击穿
用户大量请求
硬件故障可能为硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问.
缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用.
在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用.
而形成 重试加大流量 的原因有:
用户重试
代码逻辑重试
在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单.
服务调用端的会存在大量服务异常后的重试逻辑.
这些重试都会进一步加大请求流量.
最后, 服务调用者不可用 产生的主要原因是:
同步等待造成的资源耗尽
当服务调用者使用 同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了.
分别站在服务提供者和消费者的角度出发来发现应对服务失败场景的策略和方法,基本原理如下图:
对于服务提供者而言,一旦自身服务发生错误,应该快速返回合理的处理结果;
对于服务消费者而言,重点关注不要被服务提供者所产生的错误影响到自身服务的可用性。
基本策略来看,基本上包括超时、重试和异步解耦。
对于服务消费者而言,为了保护自身服务的可用性,可以使用超时机制降低它所依赖的服务对其造成的影响。同时,设置较短的超时时间有助于解决这个问题。
为降低网络瞬态异常所造成的网络通信问题,可以使用重试机制。
为降低系统耦合度,通过使用一些中间件系统实现服务提供者和服务消费者之间的异步解耦,也能把服务依赖失败的影响分摊到中间件上,从而降低服务失败的概率。
业界一些更为系统的方法和机制确保服务的可靠性有服务容错、服务隔离、服务限流和服务降级。
容错机制的基本思想是冗余和重试,即当一个服务器出现问题时不妨试试其他服务器。
集群的建立已经满足冗余的条件,而围绕如何进行重试重试就产生了集中常见的容错方式:
失效转移,指当服务调用异常时,重新在集群中查找下一个可用的服务提供者。,同时为防止无限重试,通常对失败重试最大次数进行限制。
失败通知,指当服务调用失败直接将远程调用异常通知给消费者,由消费者获取捕获异常进行后续处理。
失败安全,指当获取服务异常时,直接忽略。
通常将异常写入审计日志的媒介以方便或许依据记录找到引起异常的原因并解决。
可以简单地理解为一种简单地容错机制,为保证调用路径的完整性,在非关键环节中允许出现错误而不中断整个调用链路。
快速失败,指在获取服务调用异常时,立即报错。
彻底放弃重试机制,等同于没有容错。
在特定场景中,可使用该策略保证非核心服务只调用一次,为核心业务节约时间。
分支机制,指并行调用多个服务器,只要一个成功即可返回。
通常用于实时性要求较高的读操作,但需要浪费更多的服务资源。
广播机制,指逐个调用所有提供者,任意一台报错则报错。
通常用于通知所有提供者更新缓存或日志等本地资源信息的业务场景,不是简单地远程调用。
隔离,本质上是对系统或资源进行分割,从而实现当系统发生故障时能限定传播范围和影响范围,即发生故障后只有出问题的服务不可用,保证其他服务仍然可用。基本思路如下:
主要通过线程池进行隔离,也是实现服务隔离的基础。(可将图中隔离媒介换成线程池即可)
把业务进行分类并交给不同的线程池进行处理,当某个线程池处理一种业务请求发生问题时,不会讲故障扩散和影响到其他线程池,保证服务可用。
假设系统存在商品服务、用户服务和订单服务3个微服务,通过设置运行时环境得到3个服务一共使用200个线程,客户端调用这3个微服务共享线程池时可能会引发服务雪崩,将线程分别隔离后则不会触发整体雪崩。
把隔离媒介替换为JVM。
将系统拆分为多个子系统来实现物理隔离,各个子系统运行在独立的容器和JVM中,通过进程隔离使得一个子系统出现问题不会影响其他子系统。
将某些服务单独部署成集群,或对于某些服务可以进行分组集群管理,某一个集群出现问题之后就不会影响到其他集群,从而实现隔离。
如果有条件,对于大型高可用系统,会进行多机房部署,每个机房的服务都有自己的服务分组,本机房的服务应该只调用同机房服务。
当一个机房出现故障,将请求快速切换到其他机房确保服务继续可用。
常见的隔离技术,当用于读取操作的服务器出现故障时,写服务器照常可以运作,反之也一样。
即流量控制,限流的目的是在遇到流量高峰期或者流量突增时,把流量速率限制在系统所能接受的合理范围内,不至于将系统击垮。常见的限流方法有四种:
通过限制单位时间段内调用量来限流;
通过限制系统的并发调用程度来限流;
使用漏桶限流;
使用令牌桶算法限流
通过限制单位时间段内调用量来限流,使用一个计数器统计单位时间段某个服务的访问量,如果超过了设定的阈值,则该单位时间内不允许服务继续响应请求,或者把接下来的请求放入队列中等待下一个单位时间段继续访问,计数器在进入下一个时间段时先重置清零。
但存在十分致命临界问题,用户可以根据算法漏洞瞬间击垮应用。
假设我们规定一分钟最多接受100个请求,也就是每秒钟最多1.7个请求。
假设有一个恶意用户在0:59时瞬间发送了100个请求,并且在1:00又瞬间发送了100个请求,则该用户在1秒内瞬间发送了200个请求。即在时间窗口的重置点处集中发送请求会瞬间超过速率限制。
以上面的例子,一个时间窗口就是1分钟,然后我们将时间窗口进行划分。如果我们将滑动窗口划分成6格,所以每各代表就是10秒钟,每隔10秒钟时间窗口就会往右滑动一格,每个格子都有自己独立的计数器。当一个请求在0:35秒的时候达到,那么0:30-0:39秒对应的计数器就会加1。
看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格 子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触 发了限流。
我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。
由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:
可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。 因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率,因此,漏桶算法对于存在突发特性的流量来说缺乏效率。
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务。
牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量。
在服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略地降级,以此释放服务器资源以保证核心任务正常运行。
降级可以有计划的进行,也可以被动触发。
电商网站“双十一”期间对部分非核心业务进行手动降级
系统运行时可能出现的各种异常情况,为控制影响范围可在程序级别实现自动服务降级。
具体分级如:https://blog.csdn.net/xiaofeng10330111/article/details/85253615。
对每个微服务进行等级管理后,降级一般是从最外围、等级最低的服务开始。以移动医疗系统为例,简单分级如下:
降级有个类似的词称为服务熔断,当某个异常条件被触发,直接熔断整个服务,而不是一直等到此服务超时。
服务降级就是当某个服务熔断后,服务端准备一个本地的回退回调,返回一个缺省值。
一个基本的服务熔断器结构在实现上一般有三个状态机:
(1)Closed:熔断器关闭状态,调用失败次数积累,到了阈值(或一定比例)则启动熔断机制;
(2)Open:熔断器打开状态,此时对下游的调用都内部直接返回错误,不走网络,但设计了一个时钟选项,默认的时钟达到了一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态;
(3)Half-Open:半熔断状态,允许定量的服务请求,如果调用都成功(或一定比例)则认为恢复了,关闭熔断器,否则认为还没好,又回到熔断器打开状态;
【1】郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.
【2】https://segmentfault.com/a/1190000005988895.
【3】http://www.cnblogs.com/linjiqin/p/9707713.html.
【4】https://www.cnblogs.com/chn58/p/6566007.html.
【5】https://www.cnblogs.com/myfrank/p/7505108.html.