引入maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在配置文件,路由上添加HystrixGatewayFilterFactory过滤器,name为Hystrix。
spring:
cloud:
gateway:
routes:
- id: 用户服务
uri: lb://user-server
predicates:
- Path=/user-server/**
filters:
- name: Hystrix
args:
name: fallbackcmd
另外在配置文件上设置请求超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
# 请求超时时间5000毫秒
timeoutInMilliseconds: 5000
请求接口,并出发降级,返回如下:
{
"timestamp": "2022-08-16T01:23:11.939+0000",
"path": "/user-server/open/a",
"status": 504,
"error": "Gateway Timeout",
"message": "Response took longer than configured timeout"
}
使用自定义降级策略返回,配置文件修改,添加fallbackUri
spring:
cloud:
gateway:
routes:
- id: 用户服务
uri: lb://user-server
predicates:
- Path=/user-server/**
filters:
- name: Hystrix
args:
name: fallbackcmd
# 使用自定义fallback
fallbackUri: forward:/fallback
然后添加Fallback类
@RestController
public class FallbackController {
@RequestMapping("/fallback")
public Object fallback() {
// 这里可以自定义返回json内容
return "errorjson";
}
}
此时降级返回:
errorjson
org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory 源码
public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<HystrixGatewayFilterFactory.Config> {
public static final String FALLBACK_URI = "fallbackUri";
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public HystrixGatewayFilterFactory(
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
super(Config.class);
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
}
private DispatcherHandler getDispatcherHandler() {
if (dispatcherHandler == null) {
dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
}
return dispatcherHandler;
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY);
}
public GatewayFilter apply(String routeId, Consumer<Config> consumer) {
Config config = newConfig();
consumer.accept(config);
if (StringUtils.isEmpty(config.getName()) && !StringUtils.isEmpty(routeId)) {
config.setName(routeId);
}
return apply(config);
}
@Override
public GatewayFilter apply(Config config) {
//TODO: if no name is supplied, generate one from command id (useful for default filter)
if (config.setter == null) {
Assert.notNull(config.name, "A name must be supplied for the Hystrix Command Key");
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName());
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(config.name);
config.setter = Setter.withGroupKey(groupKey)
.andCommandKey(commandKey);
}
return (exchange, chain) -> {
RouteHystrixCommand command = new RouteHystrixCommand(config.setter, config.fallbackUri, exchange, chain);
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();
/*
* We forsake here the null check for cause as HystrixRuntimeException will
* always have a cause if the failure type is COMMAND_EXCEPTION.
*/
if (cause instanceof ResponseStatusException || AnnotatedElementUtils
.findMergedAnnotation(cause.getClass(), ResponseStatus.class) != null) {
return Mono.error(cause);
}
}
default: break;
}
}
return Mono.error(throwable);
}).then();
};
}
//TODO: replace with HystrixMonoCommand that we write
private class RouteHystrixCommand extends HystrixObservableCommand<Void> {
private final URI fallbackUri;
private final ServerWebExchange exchange;
private final GatewayFilterChain chain;
RouteHystrixCommand(Setter setter, URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain) {
super(setter);
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 (this.fallbackUri == null) {
return super.resumeWithFallback();
}
//TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
//TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri)
.host(null)
.port(null)
.uri(this.fallbackUri)
.scheme(null)
.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();
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));
}
}
public static class Config {
private String name;
private Setter setter;
private URI fallbackUri;
public String getName() {
return name;
}
public Config setName(String name) {
this.name = name;
return this;
}
public Config setFallbackUri(String fallbackUri) {
if (fallbackUri != null) {
setFallbackUri(URI.create(fallbackUri));
}
return this;
}
public URI getFallbackUri() {
return fallbackUri;
}
public void 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;
}
public Config setSetter(Setter setter) {
this.setter = setter;
return this;
}
}
}