-
2021-02-12 19:34:15
什么是服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
在官方给定的示例代码中,是这样的,通过在@HystrixCommand下面声明回退方法的名称可以实现优雅降级。也就是说当该请求发生异常时,会调用该回退方法进行返回处理。
重要的是要记住,Hystrix命令和回退应该放在同一个类中,并且具有相同的方法签名(失败的执行异常的可选参数)。
Fallback方法可以有任何访问修饰符。defaultUser在任何错误的情况下,方法将用于处理回退逻辑。如果您需要将回退方法defaultUser作为单独的Hystrix命令运行,则需要使用注释对其进行HystrixCommand注释,如下所示:
服务降级Demo
创建 Maven 项目
1、修改pom.xml文件,添加 hystrix 依赖
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.sxt
springcloud-eureka-consumer-ribbon-hystrix
0.0.1-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.5.13.RELEASE
UTF-8
UTF-8
1.8
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka-server
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-maven-plugin
2、修改Consumer的业务方法,使用 @HystrixCommand 注解完成优雅降级
以下四种情况将触发 getFallback 调用
(1) 方法抛出非 HystrixBadRequestException 异常。
(2) 方法调用超时
(3) 熔断器开启拦截调用
(4) 线程池/队列/信号量是否跑满
3、修改启动类,在启动类中开启熔断。
4、全局配置文件
spring.application.name=eureka-consumer-ribbon-hystrix
server.port=9010
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/
API 和 product参考之前女巫在说谎:Feign
正常访问测试
异常访问测试
更多相关内容 -
微服务架构(熔断机制及服务降级).pptx
2021-01-29 09:56:02微服务熔断及服务降级方案 -
37_基于dubbo如何做服务治理、服务降级以及重试?.zip
2020-11-13 23:15:14https://mp.csdn.net/console/uploadResources?spm=1011.2124.3001.4171 -
服务容错设计:流量控制、服务熔断、服务降级
2021-10-18 02:49:09单体应用的故障影响面很大,而分布式系统中由于故障的影响面可以被隔离,所以影响面较小,但是因为服务多,出故障的...而容错设计就是面向失败设计一个非常重要的环节,常见的容错设计有:流量控制、服务熔断、服务降级一、 为什么需要容错设计:
在分布式系统中,因为使用的机器和服务非常多,所以故障发生的频率会比传统的单体应用更大。只不过,单体应用的故障影响面很大,而分布式系统中由于故障的影响面可以被隔离,所以影响面较小,但是因为机器和服务多,出故障的频率也很多。不过我们需要明白下面这些道理:
- 出现故障不可怕,故障影响面过大才可怕
- 出现故障不可怕,故障恢复时间过长才可怕
另一方面,因为分布式系统的架构复杂,为了有效对分布式架构进行运维管理,我们会在系统中添加各种监控指标,方便出问题时快速定位。因此有些公司拼命地添加各种监控指标,有的甚至能添加出几万个监控指标,但这样做却给人一种”使蛮力“的感觉,一方面,信息太多就等于没有信息,另一方面,SLA 要求我们定义出核心关键指标。如果只是拼命添加各种监控指标而不定义出关键指标,这其实是一种思维上的惰性。
另外,上述的措施都是在 “救火阶段” 而不是在 “防火阶段”,所谓 “防火胜于救火”,所以我们更要考虑如何进行防火,这就要求我们在设计或者运维时都要为可能发生的故障考虑,即所谓 “Design for Failure”,面向失败设计,在设计时要考虑如何减轻故障,如果无法避免,也要使用自动化的方式恢复故障,减少故障影响面。而容错设计就是面向失败设计中一个非常重要的环节,常见的容错设计有:
(1)流控:即流量控制,根据流量、并发线程数、响应时间等指标,把随机到来的流量调整成合适的形状,即流量塑性,保证系统在流量突增情况下的可用性,避免系统被瞬时的流量高峰冲垮,一旦达到阈值则进行拒绝服务、排队等降级操作。
(2)熔断:当下游服务发生一定数量的失败后,打开熔断器,后续请求就快速失败。一段时间过后再判断下游服务是否已恢复正常,从而决定是否重置熔断器。
(3)降级:当访问量剧增、服务出现异常或者非核心服务影响到核心流程时,暂时牺牲掉一些东西,以保障整个系统的平稳运行。
(4)隔离:将系统或资源分隔开,保证系统故障时,能限定传播范围和影响范围,防止滚雪球效应,保证只有出问题的服务不可用,服务间的相互不影响。常见的隔离手段:资源隔离、线程隔离、进程隔离、集群隔离、机房隔离、读写隔离、快慢隔离、动静隔离等。
(5)超时:相当多的服务不可用问题,都是客户端超时机制不合理导致的,当服务端发生抖动时,如果超时时间过长,客户端一直处于占用连接等待响应的阶段,耗尽服务端资源,最终导致服务端集群雪崩;如果超时时间设置过短又会造成调用服务未完成而返回,所以一个健康的服务,一定要有超时机制,根据业务场景选择恰当的超时阈值。
(6)幂等:当用户多次请求同一事件时,得到的结果永远是同一个。
二、流控设计:
限流的目的是通过限制并发访问数或者时间窗口内的请求数,保证系统在流量突增情况下的稳定性,使系统不至于被高流量击垮,一旦达到限制阈值就可以拒绝服务(告知没有资源了或定向到错误页)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。而一般高并发系统常见的限流方式有:
- 限制单位时间的调用量
- 限制系统的总并发数,比如数据库连接池、线程池
- 限制时间窗口内的并发数,比如漏桶和令牌桶
下面我们就讨论各种限流方式的设计与具体应用场景:
1、限制单位时间内的调用量:
从字面上很容易理解,就是通过一个计数器来统计单位时间内某个服务的访问量。如果超过了阙值,则该单位时间段那不允许继续访问,或者把请求放入队列中等下一个时间段继续访问。需要注意的是计数器在进入下一个单位时间段时需要先清零。
对于单位时间的设置,第一不能太长,太长将导致限流效果不够“敏感”;第二不能设置得太短,越短的单位时间段将导致阈值越难设置,比如1秒钟,因为高峰期的1秒钟和低峰期的1秒钟的流量有可能相差百倍甚至千倍。最优的单位时间片段应该以阈值设置的难易程度为标准,比如我们的监控系统统计的是服务每分钟的调用量,那我们自然可以选择1分钟作为时间片段,因为我们很容易评估每个服务在高峰期和低峰期的分钟调用量,并可以通过服务在每分钟的平均耗时和异常量来评估服务在不同单位时间段的服务质量。
当单位时间段和阈值已经确定下来了,接下来就该考虑计数器的实现了,在 Java 中最常用的就是 AtomicLong,每次服务调用时,我们可以通过 AtomicLong.incrementAndGet() 方法来给计数器加1并返回最新值,并通过这个最新值和阈值来进行比较来看该服务单位时间段内是否超过了阈值。对于限制单位时间段内调用量的这种限流方式,实现简单,适用于大多数场景,但是也有一定的局限性,如果设定的阀值在单位时间段内的前几秒就被流量突刺消耗完了,将导致该时间段剩余的时间内该服务“拒绝服务”,这种现象被称为“突刺消耗”。
2、限制系统的总并发数:
这种方式通过严格限制某服务的并发访问速度,也就限制了该服务单位时间段内的访问量。相比于第一种方案,它有着更严格的限制边界,因为如果采用第一种限流方案,如果大量的服务在极短的时间产生,仍然会压垮系统,甚至雪崩。并发限流一般用于服务资源有严格的限制的场景,比如连接数,线程数等。
在 Java 中实现,并发限流也非常简单,比如可以使用信号量 Semaphore,在服务调用的入口调用非阻塞方法 Semaphore.tryAcquire() 来尝试获取一个信号量;如果获取失败,则直接返回或将调用放入到某个队列中;当服务执行完成,则使用 Semaphore.release() 来释放资源。
但这种方式仍然可以造成流量尖刺,即每台服务器上该服务的并发量从 0 上升到阀值是没有任何阻力的,因为并发量考虑的只是服务能力边界的问题。
3、通过漏桶进行限流:
漏桶算法有点像我们生活中用到的漏斗,液体倒进去漏斗后从下端小口中以固定速率流出。漏桶算法就是这样,不管流量有多大,漏桶都保证了流量的常速率输出,由于调用的消费速率已经固定,那么当桶的容量堆满时,就只能丢弃了。示意图如下:
漏桶算法是一种悲观策略,它严格地限制了系统的吞吐量,从某种角度上来说,它的效果和并发量限流很类似。漏桶算法可以用于大多数场景,但是由于它对于服务吞吐量有着严格限制,可能导致某些服务称为瓶颈。
在 Java 中想要实现一个漏桶算法,可以准备一个队列,当作桶的容量,另外通过一个计划线程池(ScheduledExecutorService)来定期从队列中获取并执行请求调用,可以一次拿多个请求,然后并发执行。
4、通过令牌桶进行限流:
令牌桶算法可以看成是漏桶算法的一种改进,漏桶算法能够强行限制请求调用的速率,而令牌桶算法能够在限制平均调用速率的同时,允许一定程度的突发调用,实现平滑限流。令牌桶算法中,桶中会有一定数量的令牌,每次请求调用需要去桶中拿取令牌,拿到令牌后才有资格执行,否则必须等待。
看到这里或许有些疑问,如果把令牌比喻成信号量,那么好像和并发限流没什么区别。其实不是,令牌桶算法的精髓在于拿令牌和放令牌的方式,这和单纯的并发限流有明显的区别:
-
每次请求时需要获取的令牌数不是固定的,比如当桶中的令牌比较多时,每次调用只需要获取一个令牌,随着令牌数的逐渐减少,当令牌使用率(使用中的令牌数/令牌总数)达到某个比率时,可能一次需要获取两个令牌,获取令牌的个数可能还会升高。
-
归还令牌有两种方法,第一种是直接放回,第二种是什么也不做,由另一个额外的令牌生成步骤将令牌允许放回桶中。
前面讲过,令牌桶允许一定程度的突发调用,那么关于令牌桶处理数据报文的方式,RFC 中定义了两种令牌桶算法:
- 单速率三色标记(single rate three color marker,srTCM,RFC2697 定义,或称为单速双桶算法)算法,主要关注报文尺寸的突发。
- 双速率三色标记(two rate three color marker,trTCM,RFC2698 定义,或称为双速双桶算法)算法,主要关注速率的突发。
两种算法都是为报文打上红、黄、绿三种颜色的标记,所以称为“三色标记”。 QoS 会根据报文的颜色,设置报文的丢弃优先级,两种算法都可工作于色盲模式和非色盲模式。对于单速率三色标记算法和双速率三色标记算法感兴趣的读者,可以阅读这篇文章:https://zhuanlan.zhihu.com/p/164503398
小结:令牌桶和漏桶算法区别:
(1)内容上:令牌桶算法是按固定速率生成令牌,请求能从桶中拿到令牌就执行,否则执行失败。漏桶算法是任务进桶速率不限,但是出桶的速率是固定的,超出桶大小的的任务丢弃,也就是执行失败。
(2)突发流量适应性上:令牌桶限制的是流入的速率且灵活,允许一次取出多个 token,只要有令牌就可以处理任务,在桶容量上允许处理突发流量。而漏桶算法出桶的速率固定,有突发流量也只能按流出的速率处理任务,所以漏桶算法是平滑流入的速率,限制流出速率。
5、四种限流算法的比较与小结:
下面给出在某种特定场景和特定参数下四种限流方式对服务并发能力影响的折线图,其中X轴表示当前并发调用数,而Y轴表示某服务在不同并发调用程度下采取限流后的实际并发调用数:
在不同场景不同参数下,服务采用所述四种限流方式都会有不同的效果,没有哪种限流算法能保证在所有场景下都是最优限流算法,因为这需要从服务资源特性、限流策略(参数)配置难度、开发难度和效果检测难度等多方面因素来考虑。但是相比于其他三种限流方式来说,令牌桶算法限流无疑是最为灵活的,因为它有着众多可配置的参数来直接影响限流的效果,比如 Google 的 Guava 包的 RateLimiter 提供了令牌桶算法的实现,感兴趣的读者可以自行百度。
最后,不论是对于令牌桶拿不到令牌被拒绝,还是漏桶的水满了溢出,或者是其他限流算法,都是为了保证大部分流量的正常使用,而牺牲掉了少部分流量,这是合理的,如果因为极少部分流量需要保证的话,那么就可能导致系统达到极限而挂掉,得不偿失。
三、熔断设计:
1、什么是熔断机制:
熔断机制可以快速地拒绝可能导致异常的调用,防止应用程序不断执行可能失败的操作,当感知到下游服务的资源出现不稳定状态(调用超时或异常比例升高时),暂时切断对下游服务的调用,而不是一直阻塞等待服务响应,阻止级联失败导致的雪崩效应,保证系统的可用性;尤其是后端太忙的时候,使用熔断设计可以保护后端不会过载。另外,对于服务间的调用一般都会设置超时与重试机制,但如果错误太多,或是在短时间内得不到修复,那么再多的重试也没有任何意义了,这时也需要使用熔断机制快速返回结果。
另外,开启熔断之后,也应该不断检测下游服务的健康情况,当检测到该节点的服务调用响应正常后,则恢复调用链路。这就要求熔断器具备感知异常的能力以及对关键逻辑实现开关控制,因此,熔断的设计有两个关键点:
- ① 判断何时熔断:客户端对每次请求服务端的正常、异常(失败、拒绝、超时)返回计数,当异常返回次数超过设定阈值时进行熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。
- ② 何时从熔断状态恢复:处于熔断状态时,客户端每隔一段时间(比如5秒),允许部分请求通过,若这部分请求正常返回,就恢复熔断。
2、熔断机制的实现:
(1)以 Hystrix 为例,Hystrix 设计了三种状态:
- ① 熔断关闭状态(Closed): 服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
- ② 熔断开启状态(Open): 在固定时间内,接口调用出错比率达到一个阈值,会进入熔断开启状态。进入熔断状态后, 后续对该服务接口的调用不再经过网络,直接执行本地的 fallback 方法。
- ③ 半熔断状态(Half-Open): 在进入熔断开启状态一段时间之后,熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
(2)阿里研发的 Sentinel 内部熔断机制的实现其实也是熔断器的思想。Sentinel 底层会通过优化的滑动窗口数据结构来实时统计所有资源的调用数据(通过 QPS、限流 QPS、异常数、RT 等)。若资源配置了熔断降级规则,那么调用时就会取出当前时刻的统计数据进行判断。当达到熔断阈值时会将熔断器置为开启状态,切断所有的请求,直到过了降级时间窗口后再重置熔断器,继续允许请求通过。
四、降级设计:
1、什么是服务降级:
所谓降级,就是指当访问量剧增、下游服务出现问题 或 非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务,这也是降级的最终目的。本质是为了解决资源不足和访问量过大的问题,当资源和访问量出现矛盾的时候,在有限的资源下,为了能够扛住大量的请求,我们就需要对系统进行降级操作,也就是暂时牺牲掉一些东西,以保障整个系统的平稳运行。一般来说,降级可以牺牲掉的东西有:
(1)降低一致性,从强一致性变成最终一致性:对于降低一致性,把强一致性变成最终一致性的做法可以有效地释放资源,并且让系统运行得更快,从而可以扛住更大的流量。通常会有两种做法:一种是简化流程的一致性(比如使用异步简化流程),一种是降低数据的一致性(比如使用缓存)。
(2)停止次要功能:停止访问不重要的功能,从而让系统释放出更多的资源。
(3)简化功能:把一些功能简化掉,比如简化业务流程,或是不再返回全量数据,只返回部分数据。
2、服务降级的类型与策略:
2.1、降级按照是否可以自动化可分为:人工开关降级和自动开关降级。
2.2、按触发降级的时机可以分为以下几个大类:
- 限流降级:当服务端请求数的数量超过配置的限制阈值,后续请求会被降级
- 超时降级:事先配置好超时时间和超时重试次数及机制,并使用异步机制探测恢复情况
- 失败降级:该方式主要是针对不稳定的API,当失败调用次数或者比例达到一定阀值时自动降级,同样要使用异步机制探测回复情况
- 故障降级:如要调用的远程服务挂掉了(比如网络故障、DNS故障、HTTP服务返回错误的状态码和RPC服务抛出异常),则可以直接降级
2.3、降级按照功能维度可分为:读服务降级和写服务降级
(1)读降级:可以暂时切换读数据来源,比如返回缓存数据、默认值、兜底数据。如果后端服务有问题,则可以降级为只读缓存,这种方式适合对读一致性要求不高的场景
① 缓存降级:使用缓存方式来降级部分读频繁的服务接口。每次服务调用成功时,记录服务的结果在缓存中,下一次失败时读取缓存的旧数据;可以根据时延要求选择本地缓存、分布式缓存、数据库或本地文件。适用场景为能够接受一定延迟的只读业务,且有足够的存储资源。但需要注意以下几点:
- 分布式缓存或数据库只是把服务故障转移到另一个依赖;
- 冷数据无法降级,即较长时间之前的状态数据无法降级;
- 降级后的数据存在一致性问题,需要根据业务情况设置合理的有效期;
- 数据量大时消耗过多的存储(特别是缓存)且命中率低,可以设置合理的缓存大小,使用LRU方式替换旧缓存。
② 默认值:直接返回配置中的默认值或者空数据。适用于弱依赖的只读业务,需要注意的是,默认值尽量有多种选择,避免千篇一律。
比如主播标语服务,在配置中心配置一批中性的默认标语,标语服务失败时直接随机取一条返回给用户,故障率不高的情况下用户基本上感知不到异常。
(2)写降级:可以使用异步处理并保证最终一致性,比如先更新缓存,然后异步同步到数据库中;或者采用事后处理,执行补偿机制,自动或者手动补偿。
在 CAP 原理和 BASE 理论中写操作存在数据一致性这个问题,降级的目的是为了提供高可用性,在多数的互联网架构中,可用性是大于数据一致性的。所以丧失写入数据的同步,通过上面的理论,我们也能勉强接受数据最终一致性。高并发场景下,如果写入操作无法及时到达或抗压,可以异步消费数据、cache更新、log等方式进行补偿
文章小结:限流、熔断、降级都是解决服务间 RPC 过程出现的异常问题,保证服务稳定性的手段。限流发生在Server端,熔断发生在Client端,而降级是触发限流、熔断之后的补偿措施。在实际场景中通常会配合实现,比如熔断和降级,熔断决定何时降级,降级是服务在熔断状态下的对外表现。
相关阅读:
常见的服务器架构入门:从单体架构、EAI 到 SOA 再到微服务和 ServiceMesh
常见分布式理论(CAP、BASE)和一致性协议(Gosssip协议、Raft一致性算法)
SpringCloud OpenFeign 远程HTTP服务调用用法与原理
Sentinel-Dashboard 与 apollo 规则的相互同步
Spring Cloud Gateway 服务网关的部署与使用详细介绍
Spring Cloud Gateway 整合 sentinel 实现流控熔断
Spring Cloud Gateway 整合 knife4j 聚合接口文档
-
服务降级 & 熔断机制
2022-03-27 20:42:33服务降级 1.什么是降级? 降级是从系统功能优先级的角度考虑如何应对系统故障。 服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心...一.服务降级
1.什么是降级?
降级是从系统功能优先级的角度考虑如何应对系统故障。
服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
降级服务的特征如下:
- 原因: 整体负荷超出整体负载承受能力。
- 目的: 保证重要或基本服务正常运行,非重要服务延迟使用或暂停使用
- 大小: 降低服务粒度,要考虑整体模块粒度的大小,将粒度控制在合适的范围内
- 可控性: 在服务粒度大小的基础上增加服务的可控性,后台服务开关的功能是一项必要配置(单机可配置文件,其他可领用数据库和缓存),可分为手动控制和自动控制。
- 次序: 一般从外围延伸服务开始降级,需要有一定的配置项,重要性低的优先降级比如可以分组设置等级1-10,当服务需要降级到某一个级别时,进行相关配置
2.服务降级的方式
- 延迟服务: 比如发表了评论,重要服务,比如在文章中显示正常,但是延迟给用户增加积分,只是放到一个缓存中,等服务平稳之后再执行。
- 在粒度范围内关闭服务(片段降级或服务功能降级): 比如关闭相关文章的推荐,直接关闭推荐区
- 页面异步请求降级: 比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;
- 页面跳转(页面降级): 比如可以有相关文章推荐,但是更多的页面则直接跳转到某一个地址。
- 写降级: 比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
- 读降级: 比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景。
3.服务降级的分类
降级按照是否自动化可分为:
- 自动开关降级(超时、失败次数、故障、限流·
- 人工开关降级(秒杀、电商大促等)
自动降级分类又分为:
- 超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
- 失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
- 故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
- 限流降级:当我们去秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时开发者会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级:降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)
4.大规模分布式系统如何降级
在大规模分布式系统中,经常会有成百上千的服务。在大促前往往会根据业务的重要程度和业务间的关系批量降级。这就需要技术和产品提前对业务和系统进行梳理,根据梳理结果确定哪些服务可以降级,哪些服务不可以降级,降级策略是什么,降级顺序怎么样,大型互联网公司基本都会有自己的降级平台,大部分降级都在平台上握作,比如手动降级开关,批量降级顺序管理,熔断阈值动态设置,限流阈值动态设置等等。
二.熔断机制
1.什么是熔断?
熔断是应对微服务雪崩效应的一种链路保护机制,类似股市、保险丝
微服务之间的数据交互是通过远程调用来完成的。服务A调用服务,服务B调用服务c,某一时间链路上对服务 C的调用响应时间过长或者服务C不可用,随着时间的增长,对服务C的调用也越来越多,然后服务C崩溃了,但是链路调用还在,对服务B的调用也在持续增多,然后服务B崩溃,随之A也崩溃,导致雪崩效应
服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
2.降级和熔断的区别
熔断和降级是两个比较容易混淆的概念,两者的含义并不相同。
降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。
3.常见的熔断器组件
SpringCloud官方目前推荐的熔断器组件如下:
- ·Hystrix
- ·Resilience4J·Sentinel
- ·SpringRetry
我们单独拎出Sentinel和Hystrix来说一下(没记错的话,Hystrix目前已经没有维护了。)。
Hystrix是Netflix开源的熔断降级组件,Sentinel是阿里中间件团队开源的一款不光具有熔断降级功能,同时还支持系统负载保护的组件。
简单来说,两者都是主要做熔断降级的,两者的差异如下:
参考文章:揭开服务降级的面纱!!! - 墨天轮
-
dubbo 服务降级
2022-02-11 09:54:15dubbo 服务降级
dubbo 服务降级
官网:https://dubbo.apache.org/zh/docs/advanced/local-mock/
服务降级
应用:消费端服务调用失败,不抛出异常,返回默认的数据
服务降级配置示例
# 同目录下查找服务降级类:com.foo.service.BarServiceMock <dubbo:reference interface="com.foo.service.BarService" mock="true" /> # 指定本地服务降级类:com.foo.service.impl.BarServiceMock <dubbo:reference interface="com.foo.service.BarService" mock="com.foo.service.impl.BarServiceMock" /> # 方法级别服务容错:方法名后面加".mock" <dubbo:reference id="demoService" check="false" interface="com.foo.BarService"> <dubbo:parameter key="sayHello.mock" value="force:return fake"/> </dubbo:reference>
mock 可选值
true:开启服务降级 false:不开启服务降级 # return:返回一个字符串表示的对象 empty:基本类型返回对应的默认值、集合类返回空值,如:return empty null:返回null true:返回true false:返回false JSON字符串:返回json字符串反序列化后的对象 # throw:抛出异常对象 抛出rpcException:<dubbo:reference interface="com.foo.BarService" mock="throw" /> 抛出自定义异常:<dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" /> # force:强制使用mock,不发起服务调用,可与return、throw组合使用 # fail:服务调用失败时,执行mock(默认行为),可与return、throw组合使用 强制返回fake:<dubbo:reference interface="com.foo.BarService" mock="force:return fake" /> 强制抛出异常:<dubbo:reference interface="com.foo.BarService" mock="force:throw com.foo.MockException" />
消费端应用启动时,在创建failoverClusterInvoker的过程中,会用MockClusterWrapper包装,调用栈如下:
# 创建FailoverClusterInvoker对象,默认创建该对象 at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.<init>(FailoverClusterInvoker.java:52) # join操作 at org.apache.dubbo.rpc.cluster.support.FailoverCluster.doJoin(FailoverCluster.java:33) at org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster.join(AbstractCluster.java:58) # MockClusterWrapper:服务降级包装类 at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper.join(MockClusterWrapper.java:39) # 创建invoker at org.apache.dubbo.registry.integration.RegistryProtocol.doCreateInvoker(RegistryProtocol.java:564) # 获取invoker at org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol.getInvoker(InterfaceCompatibleRegistryProtocol.java:58) # MigrationInvoker类 at org.apache.dubbo.registry.client.migration.MigrationInvoker.refreshInterfaceInvoker(MigrationInvoker.java:448) at org.apache.dubbo.registry.client.migration.MigrationInvoker.migrateToApplicationFirstInvoker(MigrationInvoker.java:239) # MigrationRuleHandler类 at org.apache.dubbo.registry.client.migration.MigrationRuleHandler.refreshInvoker(MigrationRuleHandler.java:73) at org.apache.dubbo.registry.client.migration.MigrationRuleHandler.doMigrate(MigrationRuleHandler.java:57) - locked <0x21db> (a org.apache.dubbo.registry.client.migration.MigrationRuleHandler) at org.apache.dubbo.registry.client.migration.MigrationRuleListener.onRefer(MigrationRuleListener.java:241) # RegistryProtocol类 at org.apache.dubbo.registry.integration.RegistryProtocol.interceptInvoker(RegistryProtocol.java:531) at org.apache.dubbo.registry.integration.RegistryProtocol.doRefer(RegistryProtocol.java:500) at org.apache.dubbo.registry.integration.RegistryProtocol.refer(RegistryProtocol.java:485) # refer操作 at org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper.refer(ProtocolListenerWrapper.java:74) at org.apache.dubbo.qos.protocol.QosProtocolWrapper.refer(QosProtocolWrapper.java:83) at org.apache.dubbo.rpc.cluster.filter.ProtocolFilterWrapper.refer(ProtocolFilterWrapper.java:71) at org.apache.dubbo.rpc.protocol.ProtocolSerializationWrapper.refer(ProtocolSerializationWrapper.java:52) at org.apache.dubbo.rpc.Protocol$Adaptive.refer(Protocol$Adaptive.java:-1) # ReferenceConfig类 at org.apache.dubbo.config.ReferenceConfig.createInvokerForRemote(ReferenceConfig.java:481) at org.apache.dubbo.config.ReferenceConfig.createProxy(ReferenceConfig.java:386) at org.apache.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:275) - locked <0x21dc> (a org.apache.dubbo.config.ReferenceConfig) at org.apache.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:216) # SimpleReferenceCache类 at org.apache.dubbo.config.utils.SimpleReferenceCache.get(SimpleReferenceCache.java:110) # DefaultModuleDeployer类 at org.apache.dubbo.config.deploy.DefaultModuleDeployer.lambda$referServices$6(DefaultModuleDeployer.java:384) at org.apache.dubbo.config.deploy.DefaultModuleDeployer$$Lambda$880/0x0000000801039dc8.accept(Unknown Source:-1) at java.util.concurrent.ConcurrentHashMap$ValuesView.forEach(ConcurrentHashMap.java:4780) # DefaultModuleDeployer类 at org.apache.dubbo.config.deploy.DefaultModuleDeployer.referServices(DefaultModuleDeployer.java:364) at org.apache.dubbo.config.deploy.DefaultModuleDeployer.start(DefaultModuleDeployer.java:151) - locked <0x21dd> (a org.apache.dubbo.config.deploy.DefaultModuleDeployer) # DubboDeployApplicationListener类 at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onContextRefreshedEvent(DubboDeployApplicationListener.java:108) at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onApplicationEvent(DubboDeployApplicationListener.java:98) at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onApplicationEvent(DubboDeployApplicationListener.java:44) # SimpleApplicationEventMulticaster类 at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) # AbstractApplicationContext类 at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378) at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:938) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) - locked <0x21de> (a java.lang.Object) # ServletWebServerApplicationContext类 at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) # SpringApplication类 at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) # 项目启动入口 at com.example.demo.DemoApplication.main(DemoApplication.java:12)
MockClusterWrapper:服务降级包装类
public class MockClusterWrapper implements Cluster { private final Cluster cluster; public MockClusterWrapper(Cluster cluster) { this.cluster = cluster; } @Override public <T> Invoker<T> join(Directory<T> directory, boolean buildFilterChain) throws RpcException { return new MockClusterInvoker<T>(directory, this.cluster.join(directory, buildFilterChain)); } //返回MockClusterInvoker对象 public Cluster getCluster() { return cluster; } }
MockClusterInvoker:发起服务调用
public class MockClusterInvoker<T> implements ClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class); private final Directory<T> directory; private final Invoker<T> invoker; public MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) { this.directory = directory; this.invoker = invoker; } public URL getUrl() { public URL getRegistryUrl() { public Class<T> getInterface() { public Directory<T> getDirectory() { public void destroy() { public boolean isDestroyed() { public boolean isAvailable() { @Override public Result invoke(Invocation invocation) throws RpcException { //服务调用 Result result; String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim(); //value:获取mock值,如果mock没有设置,默认为false if (value.length() == 0 || "false".equalsIgnoreCase(value)) { //no mock result = this.invoker.invoke(invocation); //value长度为0或者值为false,直接发起服务调用,不使用服务降级,如果调用失败会抛出异常 } else if (value.startsWith(FORCE_KEY)) { //如果value以force开头,不发起远程调用,直接服务降级 if (logger.isWarnEnabled()) { logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl()); } //force:direct mock result = doMockInvoke(invocation, null); //服务降级操作 } else { //fail-mock try { //其他先执行服务调用,调用失败则执行服务降级 result = this.invoker.invoke(invocation); //执行远程调用 //fix:#4585 if(result.getException() != null && result.getException() instanceof RpcException){ RpcException rpcException= (RpcException)result.getException(); if(rpcException.isBiz()){ throw rpcException; }else { result = doMockInvoke(invocation, rpcException); } } } catch (RpcException e) { if (e.isBiz()) { //如果是业务异常,直接抛出异常,不进行服务降级 throw e; } if (logger.isWarnEnabled()) { logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e); } result = doMockInvoke(invocation, e); //服务调用失败,进行服务降级 } } return result; //返回正常结果或者服务降级结果 } @SuppressWarnings({"unchecked", "rawtypes"}) private Result doMockInvoke(Invocation invocation, RpcException e) { //服务降级 Result result; Invoker<T> mockInvoker; List<Invoker<T>> mockInvokers = selectMockInvoker(invocation); //选择服务降级invoker if (CollectionUtils.isEmpty(mockInvokers)) { mockInvoker = (Invoker<T>) new MockInvoker(getUrl(), directory.getInterface()); } else { mockInvoker = mockInvokers.get(0); } //有多个mockInvoker,选择第一个mockInvoker try { result = mockInvoker.invoke(invocation); //服务降级调用 } catch (RpcException mockException) { if (mockException.isBiz()) { result = AsyncRpcResult.newDefaultAsyncResult(mockException.getCause(), invocation); } else { throw new RpcException(mockException.getCode(), getMockExceptionMessage(e, mockException), mockException.getCause()); } } catch (Throwable me) { throw new RpcException(getMockExceptionMessage(e, me), me.getCause()); } return result; } private String getMockExceptionMessage(Throwable t, Throwable mt) { String msg = "mock error : " + mt.getMessage(); if (t != null) { msg = msg + ", invoke error is :" + StringUtils.toString(t); } return msg; } /** * Return MockInvoker * Contract: * directory.list() will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is absent or not true in invocation, otherwise, a list of mock invokers will return. * if directory.list() returns more than one mock invoker, only one of them will be used. * * @param invocation * @return */ private List<Invoker<T>> selectMockInvoker(Invocation invocation) { List<Invoker<T>> invokers = null; //TODO generic invoker? if (invocation instanceof RpcInvocation) { //Note the implicit contract (although the description is added to the interface declaration, but extensibility is a problem. The practice placed in the attachment needs to be improved) invocation.setAttachment(INVOCATION_NEED_MOCK, Boolean.TRUE.toString()); //directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is absent or not true in invocation, otherwise, a list of mock invokers will return. try { RpcContext.getServiceContext().setConsumerUrl(getUrl()); invokers = directory.list(invocation); } catch (RpcException e) { if (logger.isInfoEnabled()) { logger.info("Exception when try to invoke mock. Get mock invokers error for service:" + getUrl().getServiceInterface() + ", method:" + invocation.getMethodName() + ", will construct a new mock with 'new MockInvoker()'.", e); } } } return invokers; } @Override public String toString() { return "invoker :" + this.invoker + ",directory: " + this.directory; } }
使用示例
***********
provider
application.yml
dubbo: application: name: dubbo-provider register-mode: instance registry: address: zookeeper://localhost:2181 group: dubbo #register-mode: instance #register: false protocol: name: dubbo port: 20880
HelloService
public interface HelloService { String hello(); }
HelloService2
public interface HelloService2 { String hello2(); }
HelloServiceImpl
@DubboService //服务暴露 public class HelloServiceImpl implements HelloService { @Override public String hello() { System.out.println("hello provider"); return "hello provider"; } }
HelloService2Impl
@DubboService //服务暴露 public class HelloService2Impl implements HelloService2 { @Override public String hello2() { /* System.out.println("hello provider2"); return "hello provider2";*/ throw new RpcException("hello2 调用出错了"); } //抛出rpcException,消费端调用时执行服务降级操作 }
DemoApplication
@EnableDubbo //扫描注册dubbo服务 @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
***********
consumer
application.yml
dubbo: application: name: dubbo-consumer registry: address: zookeeper://localhost:2181 group: dubbo #register-mode: instance protocol: name: dubbo #port: 20880 server: port: 8081
HelloService
public interface HelloService { String hello(); }
HelloService2
public interface HelloService2 { String hello2(); }
HelloService2Mock
public class HelloService2Mock implements HelloService2 { @Override public String hello2() { return "hello2 mock"; } }
HelloController
@RestController public class HelloController { @DubboReference(cluster = ClusterRules.FORKING, parameters = {"forks","3"}) private HelloService helloService; @DubboReference(mock = "true") private HelloService2 helloService2; @RequestMapping("/hello") public String hello(){ System.out.println(helloService.hello()); System.out.println(helloService2.hello2()); return "hello consumer 2"; } }
DemoApplication
@EnableDubbo //开启dubbo @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
***********
使用测试
localhost:8081/hello,控制台输出:
2022-02-11 12:26:18.615 INFO 1570 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2022-02-11 12:26:18.615 INFO 1570 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms hello provider hello2 mock
helloService2服务调用失败,执行服务降级操作
-
服务降级和服务熔断
2021-03-12 23:49:29服务熔断在微服务架构中,微服务之间的数据交互通过远程调用完成,微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,此时如果链路上某个微服务的调用响应时间过长或者不可用,那么对微服务A的调用... -
服务雪崩、服务熔断、服务降级
2021-10-12 10:46:35服务雪崩 在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程。 如存在如下调用链路: 此时,Service... -
服务降级与服务熔断区别
2021-06-09 22:49:44服务降级:系统有限的资源的合理协调 概念: 服务降级一般是指在服务器压力剧增的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以... -
服务降级的实现
2022-04-23 10:03:39# 服务降级: 站在系统整体负荷角度 实现: 关闭系统中某些边缘服务 保证系统核心服务运行 Emps 核心服务 Depts 边缘服务 # 1.客户端openfeign + hystrix实现服务降级实现 - 引入hystrix依赖 - 配置文件开启feign... -
高并发之服务降级和服务熔断____服务降级、熔断、限流的区别
2021-12-18 10:21:00高并发之服务降级和服务熔断 服务降级: 服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此环节服务器的压力,以保证核心任务的进行。 同时保证部分甚至大部分任务客户能得到正确的... -
Hystrix服务降级
2021-03-18 01:00:53## 注意Hystrix默认的服务降级时长是1秒钟.因为网络波动,这个值在正式环境中需要调整一下.## 导入包~~~org.springframework.cloudspring-cloud-starter-netflix-hystrix2.0.1.RELEASE~~~## 启动类~~~package ... -
SpringCloud之服务降级(总体第四篇)
2022-04-13 21:14:04接下来学习服务降级。 一、Hystrix 首先说明一点,它差不多已经不用了,但是为什么要说呢,因为它太牛逼了,后面很多东西都是借鉴它来的,所以必须来了解一下。 1、什么是Hystrix (1)系统分开开发后,存在了很多... -
15.0、springcloud-Hystrix-服务降级的实现
2022-04-17 17:10:3415.0、springcloud-Hystrix-服务降级的实现、以及降级与熔断的区别 先来聊一聊什么是 服务降级,比如说我们现在有三个服务器A、B、C , 当到了一个时间段,发现访问A服务器的人变得很多很多,而B和C服务器的访问... -
服务熔断和服务降级
2021-09-18 19:49:07服务熔断和服务降级 服务熔断:是发生在服务端的,当某个服务超时或者异常时,就引起熔断,类似于现实生活中的保险丝; 服务降级:是发生在客户端的,从整体网站请求负载考虑,当某个服务熔断或者关闭后,服务将... -
dubbo学习之---服务降级
2022-03-12 12:10:08今天看到dubbo相关知识后,了解了为什么要使用服务降级。 这是防止分布式服务发生雪崩效应,什么是雪崩?就是蝴蝶效应,当一个请 求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着... -
服务降级常用方案
2021-08-31 18:42:29缓存异常:假如因为缓存故障无法获取数据,在fallback逻辑中可以转而访问底层数据库(这个方法不能用在热点数据上,否则可能把数据库打挂,或者引发更大范围的服务降级和熔断,要谨慎使用)。反过来如果数据库发生... -
SpringCloud中的Hystrix服务降级、熔断、限流详细教程(服务降级篇)
2021-06-20 15:24:28首先我们先想一想分布式微服务可能存在哪些问题。 服务雪崩 多个微服务之间的调用,假设A调用B,B又调用C和D服务,C和D又分别调用其他的微服务。 那么此次请求的涉及面就越来越大,这就是“扇出”!假如当前D这个... -
【SpringCloud】微服务之服务降级、服务熔断、服务限流三板斧
2021-12-28 15:10:53文章目录一、微服务三板斧1.1 服务降级1.2 服务熔断1.3 服务熔断和服务降级的区别1.3 服务限流二、总结 一、微服务三板斧 在开发微服务系统时我们通常会面临着高并发问题的考验,为了保证服务的可用性,通常会使用... -
Hystrix服务熔断和服务降级
2022-02-10 17:19:58服务熔断—服务消费者 @RestController public class EmployeeHandler { // @HystrixCommand注解指定当前方法出问题时调用的备份方法(使用fallbackMethod属性指定) @HystrixCommand(fallbackMethod = ... -
Hystrix之服务降级代码演示
2020-04-17 13:43:40服务降级分为 服务端降级 和 客户端降级,好比一双筷子,你可以夹肉,也可以夹菜,不过一般是用来在客户端降级使用,具体情况具体分析使用。下面分别举例在服务端降级和 客户端降级代码演示。有个前提啊,就是项目中... -
10分钟带你彻底搞懂服务限流和服务降级
2021-07-26 13:42:14文章目录服务限流计数器法滑动窗口法漏桶算法令牌桶算法服务降级 我们知道,在分布式系统中,当面对高并发、大流量的应用场景时,系统就会面临崩溃的风险。这时候,为了提高系统可用性,我们就需要对外部流量进行... -
SpringCloud - Hystrix服务降级
2020-11-24 21:16:08文章目录SpringCloud - Hystrix服务降级0. 概述1. 服务降级案例1.1 创建pay生产者模块 (8007)1.2 创建order消费者模块 (80)1.3 配置服务降级(提供者和消费者)1.3.1 服务提供者(8007)进行降级保护1.3.2 服务... -
Hystrix-服务降级-服务熔断-服务限流
2020-09-07 19:40:19Hystrix简介,服务限流,服务降级,服务熔断,熔断和降级的关联,用HystrixDashboard监控请求流量以及服务健康状态,熔断的状态。 -
服务降级实战指南
2021-06-09 16:55:28上述两种场景都是服务降级。服务降级主要包括容错降级和屏蔽降级两种。 屏蔽降级 对非核心服务做强制降级,不发起远程服务调用,直接返回空、异常或者执行特定的本地逻辑,减少自身对公共资源的消费,把资源释放... -
服务降级方案
2018-11-08 12:15:57为什么需要降级:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。 降级的最终目:保证核心服务可用,即使是有损的... -
Openfeign和服务降级
2021-01-19 21:34:32服务降级 2.1局部服务降级(在服务提供方设置) 一般服务降级放在消费端,即 消费者端 ,但是提供者端一样能使用。 首先提供者,即8001 先从自身找问题,设置自身调用超时的峰值,峰值内正常运行,超出峰值需要有兜底...