本文主要解決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
邏輯很簡單,就是替換源碼的處理方式,但是,需要花一定時間,去跟蹤,希望拿去用的童鞋可以點個贊!哈哈
大部分是源碼,可以自行對比,少部分有改動,不放心我的改動可以看代碼。