本文主要解决spring cloud gateway不可以针对不同的接口进行熔断时间的设置

使用spring cloud gateway后,有了熔断,问题也就随之而来,服务间调用有了hystrix可以及时的排除坏接口、坏服务的问题,对系统很有帮助。但是!不是所有的接口都是极短时间内完成的,不是所有的接口都可以设置一样的超时时间的!

 

我们实际使用时,总有一些接口(需要交互,需要通讯,需要。。。尤其是物联网行业)

 

那么我们面临一个问题,那就是百分之99的接口都可以在1s内完美完成,但是就是那几个特殊接口,需要十几秒,几十秒的等待时间,而默认熔断的时间又只有一个。

 

前几天,查阅种种资料,问遍身边大神,都没有找到一个很好的解决办法,最后决定看源码,自己写!!!

 

废话不多说

思路:

定义一个自己的熔断策略,根据配置,设置不同的接口的熔断时间

 

想起来简单,但是真正找源码的时候需要一点时间!

贴上改完的代码:

/**
 * Copyright 2013-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Apache(源码修改 HystrixGatewayFilterFactory)
 * @ClassName MyHystrixGatewayFilterFactory
 * @Description 自定义的HystrixGatewayFilterFactory主要用来调整特殊接口的容断时间
 * @Modify TY
 * @Date 17:20 2019-07-11
 * @Version 1.0
 **/
@Component
public class MyHystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<MyHystrixGatewayFilterFactory.Config> {

    private static final String NAME = "MyHystrix";

    private final ObjectProvider<DispatcherHandler> dispatcherHandler;

    public MyHystrixGatewayFilterFactory(ObjectProvider<DispatcherHandler> dispatcherHandler) {
        super(Config.class);
        this.dispatcherHandler = dispatcherHandler;
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList(NAME_KEY);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getPath().pathWithinApplication().value();

            Map<String, Integer> timeoutMap = config.getTimeout();
            Integer timeout = null;
            if (timeoutMap != null) {
                timeout = timeoutMap.get(path);
            }

            MyRouteHystrixCommand command;
            if (timeout == null) {
                //没有定义时间的接口将使用配置的default时间
                command = new MyRouteHystrixCommand(config.getFallbackUri(), exchange, chain, path);
            } else {
                //有配置时间的接口将使用配置的时间
                command = new MyRouteHystrixCommand(config.getFallbackUri(), exchange, chain, timeout, path);
            }

            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 COMMAND_EXCEPTION: {
                            Throwable cause = e.getCause();
                            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 name() {
        return NAME;
    }

    private class MyRouteHystrixCommand extends HystrixObservableCommand<Void> {

        private final URI fallbackUri;
        private final ServerWebExchange exchange;
        private final GatewayFilterChain chain;

        public MyRouteHystrixCommand(URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain,
                                     String key) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(key))
                    .andCommandKey(HystrixCommandKey.Factory.asKey(key)));
            this.fallbackUri = fallbackUri;
            this.exchange = exchange;
            this.chain = chain;

        }

        public MyRouteHystrixCommand(URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain,
                                     int timeout,
                                     String key) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(key))
                    .andCommandKey(HystrixCommandKey.Factory.asKey(key))
                    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(timeout)));
            this.fallbackUri = fallbackUri;
            this.exchange = exchange;
            this.chain = chain;

        }

        @Override
        protected Observable<Void> construct() {
            return RxReactiveStreams.toObservable(this.chain.filter(exchange));
        }

        @Override
        protected Observable<Void> resumeWithFallback() {
            if (null == fallbackUri) {
                return super.resumeWithFallback();
            }
            URI uri = exchange.getRequest().getURI();
            boolean encoded = containsEncodedParts(uri);
            URI requestUrl = UriComponentsBuilder.fromUri(uri)
                    .host(null)
                    .port(null)
                    .uri(this.fallbackUri)
                    .build(encoded)
                    .toUri();
            exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);

            ServerHttpRequest request = this.exchange.getRequest().mutate().uri(requestUrl).build();
            ServerWebExchange mutated = exchange.mutate().request(request).build();
            DispatcherHandler dispatcherHandler = MyHystrixGatewayFilterFactory.this.dispatcherHandler.getIfAvailable();
            return RxReactiveStreams.toObservable(dispatcherHandler.handle(mutated));
        }
    }

    public static class Config {

        private String id;
        private URI fallbackUri;
        /**
         * url -> timeout ms
         */
        private Map<String, Integer> timeout;

        public String getId() {
            return id;
        }

        public Config setId(String id) {
            this.id = id;
            return this;
        }

        public URI getFallbackUri() {
            return fallbackUri;
        }

        public Config setFallbackUri(URI fallbackUri) {
            if (fallbackUri != null && !"forward".equals(fallbackUri.getScheme())) {
                throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found " + fallbackUri);
            }
            this.fallbackUri = fallbackUri;
            return this;
        }

        public Map<String, Integer> getTimeout() {
            return timeout;
        }

        public Config setTimeout(Map<String, Integer> timeout) {
            //YAML解析的时候MAP的KEY不支持'/',这里只能用'-'替代
            Map<String, Integer> tempTimeout = new HashMap<>(timeout.size());
            for (String key : timeout.keySet()) {
                Integer value = timeout.get(key);
                key = key.replace("-", "/");
                if (!key.startsWith("/")) {
                    key = "/" + key;
                }
                tempTimeout.put(key, value);
            }
            this.timeout = tempTimeout;
            return this;
        }
    }
}

有个小插曲,配置不支持接口的“/”我试了几十种办法,最后妥协了,使用“-”代替了“/”

 

配置文件

spring:
  cloud:
    #网关
    gateway:
      default-filters:
      routes:

      - id: demo-service
        #服务的application名称
        uri: lb://demo-service
        #路由级别
        order: 0
        predicates:
        #前缀
        - Path=/demo/**
        filters:
        - name: MyHystrix
          args:
            id: MyHystrix
            fallbackUri: forward:/fallback
            timeout:
              # 这里暂时用-分隔URL,因为/不支持
              demo-control-down_control: 11000
              demo-control-down_up: 11000

#设置hystrix的熔断时间
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            #设置API网关中路由转发请求的HystrixCommand执行超时时间
            timeoutInMilliseconds: 5000

逻辑很简单,就是替换源码的处理方式,但是,需要花一定时间,去跟踪,希望拿去用的童鞋可以点个赞!哈哈

大部分是源码,可以自行对比,少部分有改动,不放心我的改动可以看代码。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐