目录
前言
我们先来看,熔断降级是什么?
假设一个这样的场景,gateway网关层接入了两个应用服务,分别是服务serviceA和服务serviceB(请求流量一样),gateway目前的并发处理能力是1000,两个服务正常情况下各自处理刚刚好满足只需要使用500,现在serviceA服务因为系统问题,接口响应本来耗时50ms的变成了耗时5秒,这样因为请求堆积,gateway的1000就慢慢的被serviceA逐渐全部抢占,serviceB就分配不到资源,无法响应请求。
作为一个网关层,这是一件很糟糕的事,那么应该怎么做呢?
对,就是做隔离,gateway固定分配给serviceA最大的处理请求的线程资源只能到500,多了不给,serviceB也一样,这样互不影响,当其中一方出现问题,另一方还能正常访问。
所以我们要合理管理每个服务的请求资源,这要从几个点出发:
1、超过了500的线程处理后,其他请求怎么办:采用直接拒绝请求或者进入队列等待空闲线程处理;
2、一般情况下,预设的最大线程数是正常请求处理量的1.5倍,serviceA出现问题才会导致线程数不够用,要尽量降低问题导致的影响:设置serviceA的最大响应时间2秒,超时gateway直接断开这次跟serviceA的请求,直接响应给客户端;
3、gateway直接返回错误码给客户端有时候是不合理,会影响用户的体验:是不是可以返回定制的内容给客户端展示;
4、线程池的管理:涉及serviceA和serviceB的分组线程管理
5、请求计数管理:滑动时间窗口下正常请求数、失败请求数
6、熔断管理:时间窗口达到失败最大阈值后gateway不再请求服务,称为全断开状态,下一个时间窗口再释放一个请求作为探测服务是否已经正常,如果不正常,继续断开,如果正常,允许一部分流量请求到服务,这时候是半闭合状态,当这部分流量成功比例达到后,才允许所以流量请求,这时候是全闭合状态。
相信到这里,大家已经对熔断降级的概念有所了解,而业界目前比较成熟的熔断降级组件是Hystrix(详情可看https://github.com/Netflix/Hystrix/wiki/Configuration),而gateway这次也将会使用Hystrix来实现。
代码实战
一、引入maven包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
二、配置application.yml文件
server:
port: 8099
spring:
application:
name: gateway-frame
cloud:
gateway:
discovery:
locator:
enabled: true
# 服务名小写
lower-case-service-id: true
routes:
- id: app-service
# lb代表从注册中心获取服务,且已负载均衡方式转发
uri: lb://app-service
predicates:
- Path=/app/**
filters:
# 使用内置的HystrixGatewayFilterFactory工厂类做熔断降级
- name: Hystrix
args:
# Hystrix的bean名称
name: appHystrix
# Hystrix超时降级后调用uri地址
fallbackUri: 'forward:/gatewayFallback'
# hystrix设置隔离策略为信号量,超时时间为2秒
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 2000
# 注册中心
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://xxxxxxxx:8761/eureka/
这里采用的是springcloud gateway内置的Hystrix熔断工厂类HystrixGatewayFilterFactory,只需要配置name名称和fallbackUri,就可以实现针对当前的路由id实现熔断降级。
hystrix的相关详细配置可以参考官网(https://github.com/Netflix/Hystrix/wiki/Configuration)进行相应的配置,这里就不说了,目前设置的策略是按照信号量来统计,接口超时时间为2秒。
三、实现熔断降级后gateway的调用地址controller
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FallbackController {
@RequestMapping("/gatewayFallback")
public String gatewayFallback(){
return "{\"msg\":\"服务降级\",\"code\":200}";
}
}
四、启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
启动gateway网关服务,端口号为8099
五、编写测试服务app-service
这里直接采用springboot快速搭建web服务,注册到eureka中,然后编写测试controller类
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/client", produces = "application/json; charset=utf-8")
public class TestController {
@GetMapping(value = "/test")
public String world() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello world";
}
}
这里采用线程睡眠的方式模拟,接口响应时间为5秒
启动服务,访问接口http://localhost:8099/app/client/test,因为超时所以forward到降级接口/gatewayFallback
把接口响应时间去掉,或者调小到500毫秒,访问接口正常
源码简析
1、初始化配置类org.springframework.cloud.gateway.config.GatewayAutoConfiguration
public class GatewayAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixObservableCommand.class, RxReactiveStreams.class })
protected static class HystrixConfiguration {
@Bean
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new HystrixGatewayFilterFactory(dispatcherHandler);
}
@Bean
@ConditionalOnMissingBean(FallbackHeadersGatewayFilterFactory.class)
public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
return new FallbackHeadersGatewayFilterFactory();
}
}
}
由于类比较多内容,这里只展示跟Hystrix有关的代码。如上,当类HystrixObservableCommand.class, RxReactiveStreams.class存在时,才初始化配置HystrixGatewayFilterFactory,所以需要引入spring-cloud-starter-netflix-hystrix包;
2、我们再来看下关键工厂类HystrixGatewayFilterFactory的实现
public class HystrixGatewayFilterFactory
extends AbstractGatewayFilterFactory<HystrixGatewayFilterFactory.Config> {
@Override
public GatewayFilter apply(Config config) {
// TODO: if no name is supplied, generate one from command id (useful for default
// filter)
if (config.setter == null) {
Assert.notNull(config.name,
"A name must be supplied for the Hystrix Command Key");
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory
.asKey(getClass().getSimpleName());
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(config.name);
config.setter = Setter.withGroupKey(groupKey).andCommandKey(commandKey);
}
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return Mono.deferWithContext(context -> {
RouteHystrixCommand command = new RouteHystrixCommand(
createCommandSetter(config, exchange), config.fallbackUri,
exchange, chain, context);
return Mono.create(s -> {
Subscription sub = command.toObservable().subscribe(s::success,
s::error, s::success);
s.onCancel(sub::unsubscribe);
}).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
if (throwable instanceof HystrixRuntimeException) {
HystrixRuntimeException e = (HystrixRuntimeException) throwable;
HystrixRuntimeException.FailureType failureType = e
.getFailureType();
switch (failureType) {
case TIMEOUT:
return Mono.error(new TimeoutException());
case SHORTCIRCUIT:
return Mono.error(new ServiceUnavailableException());
case COMMAND_EXCEPTION: {
Throwable cause = e.getCause();
/*
* We forsake here the null check for cause as
* HystrixRuntimeException will always have a cause if the
* failure type is COMMAND_EXCEPTION.
*/
if (cause instanceof ResponseStatusException
|| AnnotatedElementUtils.findMergedAnnotation(
cause.getClass(),
ResponseStatus.class) != null) {
return Mono.error(cause);
}
}
default:
break;
}
}
return Mono.error(throwable);
}).then();
});
}
@Override
public String toString() {
return filterToStringCreator(HystrixGatewayFilterFactory.this)
.append("name", config.getName())
.append("fallback", config.fallbackUri).toString();
}
};
}
}
重点是里面apply方法,这里是返回Hystrix的GatewayFilter拦截器实现:return new GatewayFilter();
filter实现里面的RouteHystrixCommand就是用来执行熔断降级判断的类,这里进行了初始化:
RouteHystrixCommand command = new RouteHystrixCommand(
createCommandSetter(config, exchange),
config.fallbackUri,
exchange,
chain,
context);
其中config类的值就是用application.yml中的配置进行初始化name和fallbackUri,setter在apply方法中使用默认配置进行初始化:
public static class Config {
private String name;
private Setter setter;
private URI fallbackUri;
}
createCommandSetter(config, exchange)就是获取config中的Hystrix的配置类setter值
protected Setter createCommandSetter(Config config, ServerWebExchange exchange) {
return config.setter;
}
setter类的属性如下:
public static final class Setter {
protected final HystrixCommandGroupKey groupKey;
protected HystrixCommandKey commandKey;
protected HystrixThreadPoolKey threadPoolKey;
protected com.netflix.hystrix.HystrixCommandProperties.Setter commandPropertiesDefaults;
protected com.netflix.hystrix.HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults;
}
3、RouteHystrixCommand熔断降级关键类
RouteHystrixCommand是继承抽象类HystrixObservableCommand来实现的
private class RouteHystrixCommand extends HystrixObservableCommand<Void> {
private final URI fallbackUri;
private final ServerWebExchange exchange;
private final GatewayFilterChain chain;
private final Context context;
@Override
protected Observable<Void> construct() {
return RxReactiveStreams.toObservable(this.chain.filter(exchange).subscriberContext(context));
}
@Override
protected Observable<Void> resumeWithFallback() {
.......
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));
}
}
这个类重写了construct()构造方法和resumerWithFallback()熔断降级后的回调方法解析;
思维扩展:动态配置思路
到这里,如果我们想进自定义熔断降级加入自己的逻辑进行改造,就可以通过继承抽象类HystrixObservableCommand来实现,
实际应用场景一般是改造成动态配置化,通过数据库或者其他存储保存配置,当修改配置后,通知gateway进行Hystrix配置变更,例如,因为服务情况变更,需要调整这个routeId甚至是uri对应的降级响应返回内容、超时时间;
1、自定义自己的MyHystrixGatewayFilterFactory,在apply方法中进行初始化自定义的MyRouteHystrixCommand类;
2、这里的Setter配置初始化采用本地缓存(数据库配置变更后,通过事件event通知动态刷新本地缓存);
3、MyRouteHystrixCommand继承HystrixObservableCommand重写resumerWithFallback(),组装这个routeId和uri对应的配置标识,然后会forward到fallback接口,接口中通过配置标识查询本地缓存的配置,返回对应的降级内容。