在分布式系统中,网关做为流量的入口,所以会有大量的请求进入网关,向其余服务发起调用,其余服务不可避免的会出现调用失败(超时、异常),失败时不能让请求堆积在网关上,需要快速失败并返回给客户端,想要实现这个要求,就必须在网关上作熔断、降级操作。
概念介绍
服务降级:系统有限的资源的合理协调
概念:服务降级一般是指在服务器压力剧增的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行。
原因: 服务器的资源是有限的,而请求是无限的。在用户使用即并发高峰期,会影响整体服务的性能,严重的话会导致宕机,以至于某些重要服务不可用。故高峰期为了保证核心功能服务的可用性,就需要对某些服务降级处理。可以理解为舍小保大
应用场景: 多用于微服务架构中,一般当整个微服务架构整体的负载超出了预设的上限阈值(和服务器的配置性能有关系),或者即将到来的流量预计会超过预设的阈值时(比如双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: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: cloud-gateway
uri: http://localhost:8080
predicates:
- Path=/ytb/**
filters:
- StripPrefix=1
# 降级配置
- name: Hystrix
args:
name: testOne
# 降级接口的地址
fallbackUri: forward:/fallback
# 针对全局配置
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
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 配置的降级地址接口
@RestController
public 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、测试接口
@RestController
public 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 方式的代码
路由配置:
@Configuration
public class RouterFunctionConfiguration
{
@Autowired
private HystrixFallbackHandler hystrixFallbackHandler;
@SuppressWarnings("rawtypes")
@Bean
public RouterFunction routerFunction()
{
return RouterFunctions
.route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
hystrixFallbackHandler);
}
}
自定义 Handler:
@Component
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse>
{
private static final Logger log = LoggerFactory.getLogger(HystrixFallbackHandler.class);
@Override
public 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 再进行测试,会发现返回服务已被降级熔断。