在分布式系统中,网关做为流量的入口,所以会有大量的请求进入网关,向其余服务发起调用,其余服务不可避免的会出现调用失败(超时、异常),失败时不能让请求堆积在网关上,需要快速失败并返回给客户端,想要实现这个要求,就必须在网关上作熔断、降级操作。
概念介绍
服务降级:系统有限的资源的合理协调
概念:服务降级一般是指在服务器压力剧增的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行。
原因: 服务器的资源是有限的,而请求是无限的。在用户使用即并发高峰期,会影响整体服务的性能,严重的话会导致宕机,以至于某些重要服务不可用。故高峰期为了保证核心功能服务的可用性,就需要对某些服务降级处理。可以理解为舍小保大
应用场景: 多用于微服务架构中,一般当整个微服务架构整体的负载超出了预设的上限阈值(和服务器的配置性能有关系),或者即将到来的流量预计会超过预设的阈值时(比如双11、6.18等活动或者秒杀活动)
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
需要考虑的问题:
区分哪些服务为核心,哪些为非核心
降级策略(处理方式,一般指如何给用户友好的提示或者操作)
自动降级还是手动降
微服务架构—服务降级 这个文章讲的很详细。
服务熔断:应对雪崩效应的链路自我保护机制。可看作降级的特殊情况
概念:应对微服务雪崩效应的一种链路保护机制,类似股市、保险丝
原因: 微服务之间的数据交互是通过远程调用来完成的。服务A调用服务,服务B调用服务c,某一时间链路上对服务C的调用响应时间过长或者服务C不可用,随着时间的增长,对服务C的调用也越来越多,然后服务C崩溃了,但是链路调用还在,对服务B的调用也在持续增多,然后服务B崩溃,随之A也崩溃,导致雪崩效应
服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
在 Spring Cloud 框架里,熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
应用场景:微服务架构中,多个微服务相互调用处使用
需要考虑问题:
如何所依赖的服务对象不稳定
失败之后如何快速恢复依赖对象,如何探知依赖对象是否恢复
服务降级和服务熔断区别
触发原因不一样,服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑
管理目标层次不一样,服务熔断是一个框架层次的处理,服务降级是业务层次的处理
实现方式不一样,服务熔断一般是自我熔断恢复,服务降级相当于人工控制
触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
一句话总结
服务熔断是应对系统服务雪崩的一种保险措施,给出的一种特殊降级措施。而服务降级则是更加宽泛的概念,主要是对系统整体资源的合理分配以应对压力。
服务熔断是服务降级的一种特殊情况,他是防止服务雪崩而采取的措施。系统发生异常或者延迟或者流量太大,都会触发该服务的服务熔断措施,链路熔断,返回兜底方法。这是对局部的一种保险措施。
服务降级是对系统整体资源的合理分配。区分核心服务和非核心服务。对某个服务的访问延迟时间、异常等情况做出预估并给出兜底方法。这是一种全局性的考量,对系统整体负荷进行管理。
代码实战
了解完相关概念后,接下来我们看一下如何在 Spring Cloud Gateway 实战中配置熔断降级。
1、pom.xml 文件中添加 Hystrix 依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>
这里需要注意下,Spring Cloud 版本的问题,版本不同可能引入不成功。我这里是 Hoxton.SR6 版本
2、application.yml 配置
server:port: 8080spring:application:name: api-gatewaycloud:gateway:routes:- id: cloud-gatewayuri: http://localhost:8080predicates:- Path=/ytb/**filters:- StripPrefix=1# 降级配置- name: Hystrixargs:name: testOne# 降级接口的地址fallbackUri: forward:/fallback# 针对全局配置hystrix:command:default:execution:timeout:enabled: trueisolation:thread:timeoutInMilliseconds: 3000# 对单独的 Hystrix 的 commandKey 设置超时时间testOne:execution:isolation:thread:timeoutInMilliseconds: 3000
配置中有一个可选参数fallbackUri,当前只支持forward模式的URI。如果触发熔断,请求会被转发到该URI对应的控制器。控制器可以是自定义的fallback接口;也可以是自定义的Handler,需要实现接口org.springframework.web.reactive.function.server.HandlerFunction
还设置了接口超时时间,也就是接口响应超过这个时间就会触发熔断。
3、fallbackUri 配置的降级地址接口
@RestControllerpublic class FallbackController {@GetMapping("/fallback")public Map<String, Object> fallback() {Map<String, Object> map = new HashMap<>();map.put("code", "error");map.put("msg", "服务暂时不可用");return map;}}
4、测试接口
@RestControllerpublic class TestController {@GetMapping("/timeout")public String timeout(){try {Thread.sleep(5000);System.out.println("模拟接口超时");} catch (InterruptedException e) {e.printStackTrace();}return "请求成功";}}
Thread.sleep(5000) 用来模拟接口调用超时。
然后就可以启动项目,访问这个地址 localhost:8080/ytb/timeout
会得到以下返回结果:

因为我们设置的超时时间是 3 秒,接口睡眠 5 秒,所以就触发了降级熔断。
此时我们 application.yml 中修改一下超时时间为 6 秒,再次访问

这次就没有触发降级熔断了。
5、自定义 Handler 方式
前文提到控制器可以是自定义的fallback接口;也可以是自定义的Handler。我再贴上 Handler 方式的代码
路由配置:
@Configurationpublic class RouterFunctionConfiguration{@Autowiredprivate HystrixFallbackHandler hystrixFallbackHandler;@SuppressWarnings("rawtypes")@Beanpublic RouterFunction routerFunction(){return RouterFunctions.route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),hystrixFallbackHandler);}}
自定义 Handler:
@Componentpublic class HystrixFallbackHandler implements HandlerFunction<ServerResponse>{private static final Logger log = LoggerFactory.getLogger(HystrixFallbackHandler.class);@Overridepublic Mono<ServerResponse> handle(ServerRequest serverRequest){Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);originalUris.ifPresent(originalUri -> log.error("网关执行请求:{}失败,hystrix服务降级处理", originalUri));return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(JSON.toJSONString("{"error":"服务已被降级熔断"}")));}}
启动网关服务GatewayApplication.java,访问localhost:8080/ytb/timeout 再进行测试,会发现返回服务已被降级熔断。
