当我们使用SpringCloud Gateway 我们不可能说所有的服务接口都能在一个极短的时长内完成,如果我们统一设置了一个比较长的超时时长的话,那么就起不到超时熔断的效果了。
怎么解决呢?
在万能的百度上一番搜索后发现了两个大佬所写的文章。
链接:2.https://blog.youkuaiyun.com/u011060906/article/details/95631025
因为和我在做的业务有些差异,所以结合了他们两位的一些相关代码后解决了我现在的业务。
相关业务场景两位大佬已经说这里就不废话了。
1.首先自定义熔断工厂,里面设置了熔断name为 SpecialHystrix
package com.service.gateway.fileter;
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;
}
}
}
2.然后修改配置文件
3.gateway配置文件相关配置
cloud:
config:
fail-fast: true
name: ${spring.application.name}
profile: ${spring.profiles.active}
discovery:
enabled: true
service-id: service-config
gateway:
discovery:
locator:
enabled: true
routes:
#商品模块
- id: service-goods
uri: lb://service-goods
predicates:
- Path=/goods/**
filters:
# 限流配置
- StripPrefix=1
# 去除请求头的origin字段,此字段导致post请求
- RemoveRequestHeader=Origin
- name: RequestRateLimiter
args:
#用于限流的键的解析器的 Bean 对象名字
key-resolver: '#{@remoteAddrKeyResolver}'
#令牌桶每秒填充平均速率,暂未测试性能方面,不知道对硬件的压力有多大,这个以后再说,先设置这么大,够正常使用
redis-rate-limiter.replenishRate: 500
#令牌桶容量
redis-rate-limiter.burstCapacity: 1000
# 降级配置
- name: SpecialHystrix
args:
id: SpecialHystrix
fallbackUri: forward:/fallback
timeout:
#指定接口超时处理
#这里暂时用-分隔URL,因为/不支持
#类名-方法名
GoodsPrice-listExGoodsExport: 30000
hystrix:
command:
default: #default全局有效,service id指定应用有效
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #断路器超时时间,默认1000ms
知识有限,希望能帮到有需要的人