spring cloud gateway 指定接口熔斷時間

本文主要解決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

邏輯很簡單,就是替換源碼的處理方式,但是,需要花一定時間,去跟蹤,希望拿去用的童鞋可以點個贊!哈哈

大部分是源碼,可以自行對比,少部分有改動,不放心我的改動可以看代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章