SpringCloud学习系列Gateway-(4)熔断降级篇

目录

前言

代码实战

源码简析

思维扩展:动态配置思路


前言

我们先来看,熔断降级是什么?

假设一个这样的场景,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接口,接口中通过配置标识查询本地缓存的配置,返回对应的降级内容。

 

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值