SpringCloud Gateway 和下游服务 对单独url设置超时时间
1.场景有时业务上有需求,大部分接口响应时间都很短,就有那么几个接口比如上传下载、长轮询时间很长,如果统一把超时时间都设置长,就起不到超时熔断的效果了。2.分析从Gateway 到 下游服务,超时时间设置 共有四个地方,分别是 gateway的hystrix、gateway的ribbon(或者feign)、下游服务ribbon、下游服务的hystrix。通常来说网关的超时时间是最长...
1.场景
有时业务上有需求,大部分接口响应时间都很短,就有那么几个接口比如上传下载、长轮询时间很长,如果统一把超时时间都设置长,就起不到超时熔断的效果了。
2.分析
从Gateway 到 下游服务,超时时间设置 共有四个地方,分别是 gateway的hystrix、gateway的ribbon(或者feign)、下游服务ribbon、下游服务的hystrix。
通常来说网关的超时时间是最长的,假设链路是 网关-A服务-B服务,网关超时时间应该是调用链总和。
hystrix时间最好也大于ribbon,毕竟ribbon是接口的大概执行时间,出意外超时后,再进入熔断。
ribbon有两个超时时间:
ribbon:
ReadTimeout: 20000 #ribbon读取超时时间,接口处理时间,不包括建立连接时间
ConnectTimeout: 3000 #ribbon请求连接时间
hystrix有一个超时时间,超过此时间进入熔断。
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 2000 #超过此时间后进入熔断,这个时间应该大于后端及其ribbon的时间,否则后端接口未执行完就进入熔断
gateway在路由时可以指定 hystrixCommondKey,并且对hystrixCommondKey设置超时时间,下面key是fallbackcmd:
cloud:
gateway:
routes:
- id: frontPC
uri: lb://wisdomclass-front-pc #lb代表从注册中心获取服务,将path的请求路由到uri
predicates:
- Path=/pc/** #不想用服务名做path前缀,怕暴露
filters:
- StripPrefix=1 #除去第一个/前缀,比如请求/wisdomclass-demo/demo,会去除前缀/wisdomclass-demo,请求到路由服务的 /demo接口
- RemoveRequestHeader=Origin # 去除请求头的origin字段,此字段导致post请求 无法进入网关post熔断
- name: Hystrix #熔断
args:
name: fallbackcmd
fallbackUri: forward:/fallback #网关的统一熔断接口
hystrix:
command:
fallbackcmd:
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 2000
有时业务上有需求,大部分接口响应时间都很短,就有那么几个接口比如上传下载、长轮询时间很长,如果统一把超时时间都设置长,就起不到超时熔断的效果了。
经查询,网上有针对某个服务设置ribbon 的超时时间方法,但不满足需求。
最后找到,可以针对单独接口 设置hystrix的超时时间,ribbon没有找到有效方法,但可以暂时满足需求,将ribbon时间统一设置较长,然后hystrix针对设置。
3.解决办法
1.gateway
自定义熔断工厂,里面设置了熔断name为 SpecialHystrix
package cn.sosuncloud.wisdomclass.hystrix;
import com.netflix.hystrix.*;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Subscription;
import java.math.BigDecimal;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
@Component
public class SpecialHystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<SpecialHystrixGatewayFilterFactory.Config> {
private static final String NAME = "SpecialHystrix";
private final ObjectProvider<DispatcherHandler> dispatcherHandler;
public SpecialHystrixGatewayFilterFactory(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) {
//对rest接口通配符url进行转换 暂只配置url 末尾为数字的的接口---
path = config.wildCard(path);
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 = SpecialHystrixGatewayFilterFactory.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;
}
/** 末尾有动态传参 **/
if (key.endsWith("/")) {
key = key + "**";
}
tempTimeout.put(key, value);
}
this.timeout = tempTimeout;
return this;
}
public String wildCard(String path){
String replace = path;
String[] split = path.split("/");
if (split.length>0) {
String wildcard = split[split.length - 1];
boolean numeric = isNumeric(wildcard);
if (numeric) {
replace = path.replace(wildcard, "**");
}
}
return replace;
}
private boolean isNumeric(String str) {
String bigStr;
try {
bigStr = new BigDecimal(str).toString();
} catch (Exception e) {
return false;//异常 说明包含非数字。
}
return true;
}
}
}
路由时更换熔断类,指定路由熔断name 为 SpecialHystrix,并且指定 特殊接口的超时时间20s:
cloud:
gateway:
routes:
- id: frontPC
uri: lb://wisdomclass-front-pc #lb代表从注册中心获取服务,将path的请求路由到uri
predicates:
- Path=/pc/** #不想用服务名做path前缀,怕暴露
filters:
- StripPrefix=1 #除去第一个/前缀,比如请求/wisdomclass-demo/demo,会去除前缀/wisdomclass-demo,请求到路由服务的 /demo接口
- RemoveRequestHeader=Origin # 去除请求头的origin字段,此字段导致post请求 无法进入网关post熔断
- name: SpecialHystrix #自定义熔断
args:
id: SpecialHystrix
fallbackUri: forward:/fallback
timeout:
#指定接口超时处理
file-upload-convert: 20000
file-upload-: 20000
file-download-: 20000
course-file-upload-local-: 20000
ribbon设置时间长一些20s
ribbon:
ReadTimeout: 20000 #ribbon读取超时时间,接口处理时间,不包括建立连接时间
ConnectTimeout: 3000 #ribbon请求连接时间
OkToRetryOnAllOperations: false #网关默认开启重试,此属性设置为false 只对GET请求重试,保证幂等性
MaxAutoRetries: 1 #Max number of retries on the same server (excluding the first try)
MaxAutoRetriesNextServer: 1 #Max number of next servers to retry (excluding the first server)
ServerListRefreshInterval: 3000 # refresh the server list from the source
hystrix 设置默认超时时间2s,这样除特殊接口外,超时时间都很短
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 2000 #超过此时间后进入熔断,这个时间应该大于后端及其ribbon的时间,否则后端接口未执行完就进入熔断
2.下游服务
hystrix,设置默认超时时间 HystrixCommandKey为default,设置特殊接口超时时间,HystrixCommandKey为类名#方法名(参数类型,参数类型)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 #超过此时间后进入熔断
CourseFileRemote#uploadByLocal(MultipartFile,Long):
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 20000
FilesRemote#fileUpload(MultipartFile,String):
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 20000
ribbon的时间也长一些
ribbon:
ReadTimeout: 20000 #ribbon读取超时时间,接口处理时间,不包括建立连接时间
ConnectTimeout: 3000 #ribbon请求连接时间
OkToRetryOnAllOperations: false #网关默认开启重试,此属性设置为false 只对GET请求重试,保证幂等性
MaxAutoRetries: 1 #Max number of retries on the same server (excluding the first try)
MaxAutoRetriesNextServer: 1 #Max number of next servers to retry (excluding the first server)
ServerListRefreshInterval: 3000 # refresh the server list from the source
更多推荐
所有评论(0)