最近这段时间在公司内部分享了Spring Cloud的一些功能,结合目前公司使用的框架,针对这段时间调研的SC(Spring Cloud)技术,对现有的架构中融入了一些自定义的功能.spring
目前公司使用的架构是Spring Cloud Zuul+dubbo ,使用docker容器,k8s对容器进行管理,咱们主要看Zuul,后面考虑把Hystrix整合进dubbo.由于咱们不是彻底用的sc的一套功能,只是用了sc的网关,使用网关对请求进行路由到服务,虽然sc默认包含有ribbon和hystrix功能,可是只限于面向服务的路由,而咱们服务没有整合到eureka中,因此咱们用的是传统路由模式,路由转发地址是svc的地址,至关于默认已经有了负载均衡的功能,因此我不须要使用ribbon,只须要使用hystrix.docker
对zuul比较了解的同窗知道,zuul使用传统路由模式和使用面向服务的路由模式分别由SimpleHostRoutingFilter
和RibbonRoutingFilter
两个route类型的过滤器进行路由的,因此咱们首先须要分析这二者之间的差异apache
此过滤器是传统路由方式的route过滤器,其shouldFilter()方法以下:架构
@Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); }
能够发现,决定是否过滤的关键条件是上下文中是否含有传统路由的URL.因此,此过滤器处理的是传统路由模式的请求过滤.app
经过查看其构造器,不难发现,其初始化是在ZuulProxyAutoConfiguration
中,传入了ZuulProperties
zuul的一些配置信息,来初始化这个filter,而后还有一堆http链接管理工厂以及一个ProxyRequestHelper
bean来处理一些request和response的请求和响应.负载均衡
查看其核心run()
方法,不难发现,其主要执行逻辑就是构建了一个httpClient,而后封装请求信息,执行请求获取响应而后返回,而且将请求信息设置到RequestContext中,返回请求结果到客户端,有兴趣的同窗能够看下源码:框架
@Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); MultiValueMap<String, String> headers = this.helper .buildZuulRequestHeaders(request); MultiValueMap<String, String> params = this.helper .buildZuulRequestQueryParams(request); String verb = getVerb(request); InputStream requestEntity = getRequestBody(request); if (getContentLength(request) < 0) { context.setChunkedRequestBody(); } String uri = this.helper.buildZuulRequestURI(request); this.helper.addIgnoredHeaders(); try { //封装请求信息,执行请求获取响应 CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); //设置请求结果 setResponse(response); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } return null; }
因此,能够看出之因此传统路由模式没有hystrix和ribbon功能,是由于它直接是使用的apache的HttpClient来执行请求转发,返回结果直接响应,没有对请求作拦截,也没有对请求做熔断处理,简单粗暴.ide
此过滤器是面向服务路由的过滤器,老规矩,shouldFilter()
:ui
@Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null && ctx.sendZuulResponse()); }
判断此过滤器是否执行的关键条件是上下文中不含有传统路由的URL,而且服务id不能为空.this
再看看其构造器,一样的,此构造器的初始化也是在ZuulProxyAutoConfiguration
中,所不一样的是,注入了一个RibbonCommandFactory
,这个就是RibbonReoutingFilter
的核心,看这个名字,用屁股都能想到确定是和ribbon还有hystrix有关.
再看看它的run()方法:
@Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); this.helper.addIgnoredHeaders(); try { //构建一些请求信息 RibbonCommandContext commandContext = buildCommandContext(context); ClientHttpResponse response = forward(commandContext); setResponse(response); return response; } catch (ZuulException ex) { throw new ZuulRuntimeException(ex); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } }
这里看到,主要就是作了一个操做,构建了一个RibbonCommandContext
上下文对象,存放了一些serviceId,meothod,uri,header...的一些请求信息而后拿这些信息去执行forward(commandContext)
,那咱们就看看forward()
去解解毒:
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception { Map<String, Object> info = this.helper.debug(context.getMethod(), context.getUri(), context.getHeaders(), context.getParams(), context.getRequestEntity()); RibbonCommand command = this.ribbonCommandFactory.create(context); try { ClientHttpResponse response = command.execute(); this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders()); return response; } catch (HystrixRuntimeException ex) { return handleException(info, ex); } }
这个方法就是用上面的请求信息和构造器传入的RibbonCommandFactory
建立了一个command对象,而后经过此对象去执行操做而后返回response到客户端.
看起来貌似和以前的SimpleHostRoutingFilter
流程差不了太多,都是构建请求信息,而后获取响应,而后响应到客户端,那么为么比这个RibbonRoutingFilter
就是吊一些呢,又有熔断又有负载均衡的.因此这里就能够破案了,他的这些个功能就是由于这个command对象,下面咱们就去看看这个command究竟是什么.
public interface RibbonCommand extends HystrixExecutable<ClientHttpResponse> { }
看到这里可能有的人就了然了,这个HystrixExecutable
是为HystrixCommand
设计的接口,主要提供执行命令的抽象方法,例如:execute()
,queue()
,observe()
,因此他才能有熔断的功能,那么负载均衡呢,我猜八成是和上面的RibbonCommandContext
有关,由于这个context对象里面放了serviceId,说明了实例的选择都是这里面操做的.
下面咱们去看看这段代码ribbonCommandFactory.create(context)
->org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory#create
,
@Override @SuppressWarnings("deprecation") public RestClientRibbonCommand create(RibbonCommandContext context) { String serviceId = context.getServiceId(); //根据serviceId 获取服务降级的provider,供后面调用服务发生异常的时候调用 ZuulFallbackProvider fallbackProvider = getFallbackProvider(serviceId); RestClient restClient = this.clientFactory.getClient(serviceId, RestClient.class); return new RestClientRibbonCommand(context.getServiceId(), restClient, context, this.zuulProperties, fallbackProvider, clientFactory.getClientConfig(serviceId)); }
这里获取了服务降级的provider,建立了发送请求的RestClient
,实例化了RestClientRibbonCommand
,而且将配置注入进去了这个command.
//RestClientRibbonCommand的构造器 public RestClientRibbonCommand(String commandKey, RestClient client, RibbonCommandContext context, ZuulProperties zuulProperties, ZuulFallbackProvider zuulFallbackProvider, IClientConfig config) { super(commandKey, client, context, zuulProperties, zuulFallbackProvider, config); } //AbstractRibbonCommand的构造器,是RestClientRibbonCommand的构造器的抽象父类 protected AbstractRibbonCommand(Setter setter, LBC client, RibbonCommandContext context, ZuulFallbackProvider fallbackProvider, IClientConfig config) { super(setter); this.client = client; this.context = context; this.zuulFallbackProvider = fallbackProvider; this.config = config; } //初始化HystrixCommand的配置信息 protected static Setter getSetter(final String commandKey,ZuulProperties zuulProperties, IClientConfig config) { // @formatter:off Setter commandSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand")) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); final HystrixCommandProperties.Setter setter = createSetter(config, commandKey, zuulProperties); if (zuulProperties.getRibbonIsolationStrategy() == ExecutionIsolationStrategy.SEMAPHORE){ final String name = ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores"; // we want to default to semaphore-isolation since this wraps // 2 others commands that are already thread isolated final DynamicIntProperty value = DynamicPropertyFactory.getInstance() .getIntProperty(name, zuulProperties.getSemaphore().getMaxSemaphores()); setter.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get()); } else if (zuulProperties.getThreadPool().isUseSeparateThreadPools()) { final String threadPoolKey = zuulProperties.getThreadPool().getThreadPoolKeyPrefix() + commandKey; commandSetter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); } return commandSetter.andCommandPropertiesDefaults(setter); // @formatter:on
}
这里能够看到,在初始化RestClientRibbonCommand
时,调用了父类的构造器,而后将HystrixCommand
初始化,将zuul的配置信息配置到HystrixCommand
中,这里看到,在Zuul中,Hystrix默认的隔离模式就是信号量隔离,并且commandKey
就是serviceId
.至此,Hystrix初始化完成,而后经过上文的command.execute()
方法来进行调用,也就是调用run()
方法,因此,这里咱们看看:
@Override protected ClientHttpResponse run() throws Exception { final RequestContext context = RequestContext.getCurrentContext(); RQ request = createRequest(); RS response; boolean retryableClient = this.client instanceof AbstractLoadBalancingClient && ((AbstractLoadBalancingClient)this.client).isClientRetryable((ContextAwareRequest)request); if (retryableClient) { response = this.client.execute(request, config); } else { response = this.client.executeWithLoadBalancer(request, config); } context.set("ribbonResponse", response); // Explicitly close the HttpResponse if the Hystrix command timed out to // release the underlying HTTP connection held by the response. // if (this.isResponseTimedOut()) { if (response != null) { response.close(); } } return new RibbonHttpResponse(response); }
这里run()
方法就是核心的调用逻辑了,这里首先会判断client是否支持重试,若是请求能够重试的话就执行重试逻辑,具体重试的机制能够看这篇文章:http://blog.didispace.com/spring-cloud-zuul-retry-detail/,同时不是重试的话就会执行executeWithLoadBalancer()
从而达到负载均衡的目的.
至此关于route过滤器的两种路由模式已经梳理完毕:
SimpleHostRoutingFilter
不一样的是,他建立了一个RibbonCommand
对象,这个command本质就是HystrixCommand,在HystrixCommand的run()
方法内发送请求,并在此方法内经过serviceId和client进行负载均衡调用.细心的同窗可能会发现:RibbonRoutingFilter
其实没有对返回的response做任何处理,也就是说,就算被路由的服务端发生了错误,也是返回response,错误都在response里面包着,正常状况下不会抛出异常,固然也不会降级熔断了
经过上面的分析,想要对传统路由模式进行改造添加熔断功能的话,就须要重写SimpleHostRoutingFilter
,在其做http调用的时候添加熔断功能,而后将返回调用的结果set到RequestContext
中,这样就完成了路由模式的改造
首先,咱们建立一个bean,继承自SimpleHostRoutingFilter
,查看SimpleHostRoutingFilter
发现他的成员变量都是私有的,子类没法共享这些变量,因此,咱们只能再定义这些变量,这些变量大多都是httpClient
相关的一些配置类,固然咱们也能够选择不使用httpClient
使用其余的http调用Client好比RestTemplate
,可是为了作到尽可能少的改动,并且让以前的配置改造以后也可使用,因此我选择没有动他的这套逻辑
那么咱们只须要重写一些私有的成员变量和一些私有方法就能够了,固然run()方法也是要重写的,我就暴力一点,直接重写全部的方法,而后改动一些地方就行了,改动后的代码以下:
@Component @ConditionalOnExpression("${host.route.hystrix.enable:false}")//自定义路由模式的开关 public class SimpleHostHystrixRoutingFilter extends SimpleHostRoutingFilter { private static final Log log = LogFactory.getLog(SimpleHostHystrixRoutingFilter.class); private final Timer connectionManagerTimer = new Timer( "SimpleHostRoutingFilter.connectionManagerTimer", true); private boolean sslHostnameValidationEnabled; private ProxyRequestHelper helper; private ZuulProperties.Host hostProperties; private ApacheHttpClientConnectionManagerFactory connectionManagerFactory; private HttpClientConnectionManager connectionManager; private CloseableHttpClient httpClient; private boolean customHttpClient = false; private HostHystrixCommandFactory restClientHystrixCommandFactory; @EventListener public void onPropertyChange(EnvironmentChangeEvent event) { if (!customHttpClient) { boolean createNewClient = false; for (String key : event.getKeys()) { if (key.startsWith("zuul.host.")) { createNewClient = true; break; } } if (createNewClient) { try { SimpleHostHystrixRoutingFilter.this.httpClient.close(); } catch (IOException ex) { log.error("error closing client", ex); } SimpleHostHystrixRoutingFilter.this.httpClient = newClient(); } } } public SimpleHostHystrixRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties, ApacheHttpClientConnectionManagerFactory connectionManagerFactory, ApacheHttpClientFactory httpClientFactory, HostHystrixCommandFactory restClientHystrixCommandFactory) { super(helper, properties, connectionManagerFactory, httpClientFactory); this.helper = helper; this.hostProperties = properties.getHost(); this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled(); this.connectionManagerFactory = connectionManagerFactory; this.restClientHystrixCommandFactory = restClientHystrixCommandFactory; } @PostConstruct private void initialize() { if (!customHttpClient) { this.connectionManager = connectionManagerFactory.newConnectionManager( !this.sslHostnameValidationEnabled, this.hostProperties.getMaxTotalConnections(), this.hostProperties.getMaxPerRouteConnections(), this.hostProperties.getTimeToLive(), this.hostProperties.getTimeUnit(), null); this.httpClient = newClient(); this.connectionManagerTimer.schedule(new TimerTask() { @Override public void run() { if (SimpleHostHystrixRoutingFilter.this.connectionManager == null) { return; } SimpleHostHystrixRoutingFilter.this.connectionManager.closeExpiredConnections(); } }, 30000, 5000); } } @PreDestroy public void stop() { this.connectionManagerTimer.cancel(); } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); log.info("======>custom hystrix host routing filter start ,context :{}"+ context); HttpServletRequest request = context.getRequest(); MultiValueMap<String, String> headers = this.helper .buildZuulRequestHeaders(request); MultiValueMap<String, String> params = this.helper .buildZuulRequestQueryParams(request); String verb = getVerb(request); InputStream requestEntity = getRequestBody(request); URL host = RequestContext.getCurrentContext().getRouteHost(); String uri = this.helper.buildZuulRequestURI(request); uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/")); int contentLength = request.getContentLength(); ContentType contentType = null; if (request.getContentType() != null) { contentType = ContentType.parse(request.getContentType()); } InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength, contentType); if (request.getContentLength() < 0) { context.setChunkedRequestBody(); } this.helper.addIgnoredHeaders(); HttpRequest httpRequest = super.buildHttpRequest(verb, uri, entity, headers, params, request); //routeId做为Hystrix 的 commandKey, // eg. zuul.routes.hystrix.path=/hystrix-provider/** 的routeId是hystrix String routeId = (String) context.get("proxy"); try { //SEMAPHORE 隔离模式共享的是一个线程的requestContext ,可是thread 模式使用的是另一个独立的线程池里面的线程,requestContext信息不共享,须要将context 经过构造器传入 HostHystrixCommand command = restClientHystrixCommandFactory.create(routeId, httpClient,httpRequest,getHttpHost(host)); HttpResponse response = command.execute(); setResponse(response); } catch (Exception ex) { if((ex instanceof HystrixRuntimeException) && (ex.getCause() instanceof CircuitBreakerResponseException)){ HttpResponse httpResponse = ((CircuitBreakerResponseException) ex.getCause()).getHttpResponse(); try { setResponse(httpResponse); } catch (IOException e) { e.printStackTrace(); } return null; } throw new ZuulRuntimeException(ex); } return null; } private MultiValueMap<String, String> revertHeaders(Header[] headers) { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); for (Header header : headers) { String name = header.getName(); if (!map.containsKey(name)) { map.put(name, new ArrayList<>()); } map.get(name).add(header.getValue()); } return map; } private InputStream getRequestBody(HttpServletRequest request) { InputStream requestEntity = null; try { requestEntity = request.getInputStream(); } catch (IOException ex) { // no requestBody is ok. } return requestEntity; } private String getVerb(HttpServletRequest request) { String sMethod = request.getMethod(); return sMethod.toUpperCase(); } private void setResponse(HttpResponse response) throws IOException { RequestContext.getCurrentContext().set("zuulResponse", response); this.helper.setResponse(response.getStatusLine().getStatusCode(), response.getEntity() == null ? null : response.getEntity().getContent(), revertHeaders(response.getAllHeaders())); } private HttpHost getHttpHost(URL host) { HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(), host.getProtocol()); return httpHost; } }
这里的构造器我比父类多传了一个RestClientHystrixCommandFactory
,这个Factory就是建立HystrixCommand
的工厂bean,里面实例化了一个自定义的HystrixCommand
,run()
方法就是经过这个工厂bean建立了一个HystrixCommand
,而后进行调用的.
再看RestClientHystrixCommandFactory
:
@Component public class RestClientHystrixCommandFactory { @Autowired(required = false) private List<HostFallbackProvider> fallbackList = Collections.emptyList(); //TODO:暂时只用默认的errorDecode @Autowired DefaultHttpClientErrorDecoder errorDecoder; private Map<String, HostFallbackProvider> fallbackProviderMap; @Autowired private ZuulProperties zuulProperties; @PostConstruct public void init() { this.fallbackProviderMap = fallbackList.stream().collect(Collectors.toMap(HostFallbackProvider::getRoute, x -> x, (x, y) -> x)); } public RestClientHystrixCommand create(String routeId, CloseableHttpClient httpClient, HttpRequest httpRequest, HttpHost httpHost) { HostFallbackProvider fallbackProvider = null; if (!fallbackProviderMap.isEmpty()) { if (Objects.isNull(fallbackProvider = fallbackProviderMap.get("*"))) { fallbackProvider = fallbackProviderMap.get(routeId); } } return new RestClientHystrixCommand(routeId, httpClient,httpRequest,httpHost,fallbackProvider,errorDecoder,zuulProperties); } }
这个Factory就是作了两个操做:
ErrorDecoder
,对请求调用返回的结果进行decode,判断是否须要抛出异常触发熔断/降级,目前没有考虑把这个decoder作成定制化,因此如今默认都是用的我定义的一套ErrorDecoder
.public interface HostFallbackProvider { /** * * @Description: 返回routId,代表为哪一个rout提供回退,* 表示 为全部route提供回退 * @Param: * @returns: * @Date: 2019/12/4 */ String getRoute(); HttpResponse fallbackResponse(Throwable cause); HttpResponse fallbackResponse(); }
这个HostFallbackProvider
是我参考RibbonRoutingFilter
提供的FallbackProvider
和ZuulFallbackProvider
实现的一个接口,只不过FallbackProvider
返回的是ClientHttpResponse
,而我这里返回的是HttpResponse
,功能都是一致的
再看DefaultHttpClientErrorDecoder
,这个是我默认实现的一套逻辑:针对4系列的异常不作处理,也不触发熔断和降级,5系列的异常咱们就直接触发抛出一个自定义的异常,而且将response装在异常里面返回出去
/** * Created by liuwen on 2019/12/3 * httpClient 响应错误处理接口,能够经过实现此方法配置httpClient发生错误(4xx/5xx)异常的错误解析, * 从而决定是否走熔断处理,也能够用于异常信息的传递 * */ public interface HttpClientErrorDecoder { boolean hashError(HttpResponse httpResponse); void handlerError(HttpResponse httpResponse) throws Exception; } @Component public class DefaultHttpClientErrorDecoder implements HttpClientErrorDecoder { @Override public boolean hashError(HttpResponse httpResponse) { int stateSeries = getStateSeries(httpResponse); return stateSeries == HttpStatus.Series.SERVER_ERROR.value() || stateSeries == HttpStatus.Series.CLIENT_ERROR.value(); } private int getStateSeries(HttpResponse httpResponse) { return httpResponse.getStatusLine().getStatusCode() / 100; } @Override public void handlerError(HttpResponse httpResponse) throws Exception { int stateSeries = getStateSeries(httpResponse); //4系列的异常不走熔断 if (stateSeries == HttpStatus.Series.SERVER_ERROR.value()) { throw new CircuitBreakerResponseException(httpResponse, "internal server error"); } } }
自定义的异常很简单:
@Data public class CircuitBreakerResponseException extends RuntimeException { private HttpResponse httpResponse; public CircuitBreakerResponseException(HttpResponse response, String message) { super(message); this.httpResponse = response; } }
接下来就是核心了,咱们的自定义的RestClientHystrixCommand
,仔细看这个Command,在构造器里面初始化了父类的构造参数,和RestClientRibbonCommand
相似,也是定义了使用HystrixCommand
时的一些默认配置,以及引用了一些zuul的配置bean去初始化HystrixCommand
.
他的run()
方法就是执行了http请求,而后对响应结果进行了decode,同时,因为咱们使用的原生的HttpClient
,因此,必定要关闭HttpClient
,防止它占用太多的资源,致使链接一直没法释放,从而形成后续的请求因为没有链接使用而一直阻塞,致使系统没法使用,虽然在SimpleHostHystrixRoutingFilter
里有一个定时器去定时去关闭链接,可是这种没有被使用的链接是没法关闭的,因此这里必定要在调用超时的状况下关闭链接
fallBack()
,这里的fallBack()
就是使用了以前在RestClientHystrixCommandFactory
里面传入的HostFallbackProvider
bean,而后将fallBack逻辑交给HostFallbackProvider
提供的方法中处理,从而达到定制化的降级的目的
@CommonsLog public class RestClientHystrixCommand extends HystrixCommand<HttpResponse> { private final HttpRequest httpRequest; private final HttpHost httpHost; private CloseableHttpClient httpclient; private HostFallbackProvider fallbackProvider; private HttpClientErrorDecoder errorDecoder; public RestClientHystrixCommand(String commandKey, CloseableHttpClient httpClient, HttpRequest httpRequest, HttpHost httpHost, HostFallbackProvider fallbackProvider, DefaultHttpClientErrorDecoder errorDecoder,ZuulProperties zuulProperties) { //初始化父类默认构造参数 super(getSetter(commandKey,zuulProperties)); this.httpclient = httpClient; this.httpRequest = httpRequest; this.httpHost = httpHost; this.fallbackProvider = fallbackProvider; this.errorDecoder = errorDecoder; } /** * @Description: 构造HystrixCommand配置 * <p> * 注意: * zuul里,从新封装了hytrix的一些配置名称,致使hytrix的一些原生全局配置会失效,须要经过zuulProperties从新设置的属性以下: * 1.隔离级别指定:zuul.ribbonIsolationStrategy=SEMAPHORE * 2.信号隔离的默认隔离大小:zuul.semaphore.max-semaphores=20 * 而原生的hytrix.command.default.execution.isolation.strategy和maxConcurrentRequests的配置将失效,会被这2个覆盖 * * @Param: * @returns: * @Date: 2019/11/20 */ protected static HystrixCommand.Setter getSetter(String commandKey, ZuulProperties zuulProperties) { Setter commandSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand")) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); final HystrixCommandProperties.Setter setter = HystrixCommandProperties.Setter().withExecutionIsolationStrategy(zuulProperties.getRibbonIsolationStrategy()); if (zuulProperties.getRibbonIsolationStrategy() == HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE) { final String name = ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores"; final DynamicIntProperty value = DynamicPropertyFactory.getInstance() .getIntProperty(name, zuulProperties.getSemaphore().getMaxSemaphores()); setter.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get()); } else if (zuulProperties.getThreadPool().isUseSeparateThreadPools()) { final String threadPoolKey = zuulProperties.getThreadPool().getThreadPoolKeyPrefix() + commandKey; commandSetter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); } return commandSetter.andCommandPropertiesDefaults(setter); } @Override protected HttpResponse run() throws Exception { return forward(); } protected HttpResponse forward() throws Exception { CloseableHttpResponse response = httpclient.execute(httpHost, httpRequest); if(isResponseTimedOut()){ response.close(); } //处理返回响应,触发熔断或者传递异常 if (this.errorDecoder.hashError(response)) { this.errorDecoder.handlerError(response); } return response; } /** * @Description: 重写getFallback方法, * @Param: * @returns: * @Date: 2019/11/21 */ @Override protected HttpResponse getFallback() { log.error("====>some error was happened in this request,execute fallback method"); Throwable throwable = super.getExecutionException(); if (!Objects.isNull(fallbackProvider)) { if ((throwable == null ? super.getExecutionException() : throwable) == null) { return fallbackProvider.fallbackResponse(); } else { return fallbackProvider.fallbackResponse(throwable); } } return super.getFallback(); } }
至此,咱们的一套定制化的传统路由模式增长熔断的功能已经实现完毕,除了可使用到以前的host的一套配置以外,也可使用ribbon的一些关于断路器的配置,并且弥补了以前RibbonRoutingFilter
的请求触发降级/熔断的缺陷,使用者能够定制化的针对不一样的路由id进行定制化的开发本身的模块的解码和降级策略.