SpringCloudGateway重试机制原理解析
重试,我相信⼤家并不陌⽣。在我们调⽤Http接⼝的时候,总会因为某种原因调⽤失败,这个时候我们可以通过重试的⽅式,来重新请求接⼝。
⽣活中这样的事例很多,⽐如打电话,对⽅正在通话中啊,信号不好啊等等原因,你总会打不通,当你第⼀次没打通之后,你会打第⼆次,第三次…第四次就通了。
重试也要注意应⽤场景,读数据的接⼝⽐较适合重试的场景,写数据的接⼝就需要注意接⼝的幂等性了。还有就是重试次数如果太多的话会导致请求量加倍,给后端造成更⼤的压⼒,设置合理的重试机制才是最关键的。
今天我们来简单的了解下Spring Cloud Gateway中的重试机制和使⽤。
使⽤讲解
RetryGatewayFilter是Spring Cloud Gateway对请求重试提供的⼀个GatewayFilter Factory。
配置⽅式:
spring:
cloud:
gateway:
routes:
- id: fsh-house
uri: lb://fsh-house
predicates:
- Path=/house/**
filters:
- name: Retry
args:
retries: 3
series:
- SERVER_ERROR
statuses:
- OK
methods:
- GET
- POST
exceptions:
- java.io.IOException
配置讲解
配置类源码:org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory.RetryConfig:
public static class RetryConfig {
private int retries = 3;
private List<Series> series = toList(Series.SERVER_ERROR);
private List<HttpStatus> statuses = new ArrayList<>();
private List<HttpMethod> methods = toList(HttpMethod.GET);
private List<Class<? extends Throwable>> exceptions = toList(IOException.class);
// .....
}
retries:重试次数,默认值是3次
series:状态码配置(分段),符合的某段状态码才会进⾏重试逻辑,默认值是SERVER_ERROR,值是5,也就是5XX(5开头的状态码),共有5个值:
public enum Series {
INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);
}
statuses:状态码配置,和series不同的是这边是具体状态码的配置,取值请参考:org.springframework.http.HttpStatus
methods:指定哪些⽅法的请求需要进⾏重试逻辑,默认值是GET⽅法,取值如下:
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}
exceptions:指定哪些异常需要进⾏重试逻辑,默认值是java.io.IOException
代码测试
就写个接⼝,在接⼝中记录请求次数,然后抛出⼀个异常模拟500,通过⽹关访问这个接⼝,如果你配置了重试次数是3,那么接⼝中会输出4次结果才是对的,证明重试⽣效了。
AtomicInteger ac = new AtomicInteger();
@GetMapping("/data")
public HouseInfo getData(@RequestParam("name") String name) {
if (StringUtils.isBlank(name)) {
throw new RuntimeException("error");
}
return new HouseInfo(1L, "上海", "虹⼝", "XX⼩区");
}
源码欣赏
@Override
public GatewayFilter apply(RetryConfig retryConfig) {
// 验证重试配置格式是否正确
retryConfig.validate();
Repeat<ServerWebExchange> statusCodeRepeat = null;
if (!Statuses().isEmpty() || !Series().isEmpty()) {
Predicate<RepeatContext<ServerWebExchange>> repeatPredicate = context -> {
ServerWebExchange exchange = context.applicationContext();
// 判断重试次数是否已经达到了配置的最⼤值
if (exceedsMaxIterations(exchange, retryConfig)) {
return false;
}
// 获取响应的状态码
HttpStatus statusCode = Response().getStatusCode();
// 获取请求⽅法类型
HttpMethod httpMethod = Request().getMethod();
// 判断响应状态码是否在配置中存在
boolean retryableStatusCode = Statuses().contains(statusCode);
if (!retryableStatusCode && statusCode != null) { // null status code might mean a network exception?
// try the series
retryableStatusCode = Series().stream()
.anyMatch(series -> statusCode.series().equals(series));
}
// 判断⽅法是否包含在配置中
boolean retryableMethod = Methods().contains(httpMethod);
reactor debug mode is enabled// 决定是否要进⾏重试
return retryableMethod && retryableStatusCode;
};
statusCodeRepeat = lyIf(repeatPredicate)
.doOnRepeat(context -> reset(context.applicationContext()));
}
/
/TODO: support timeout, backoff, jitter, in Builder
Retry<ServerWebExchange> exceptionRetry = null;
if (!Exceptions().isEmpty()) {
Predicate<RetryContext<ServerWebExchange>> retryContextPredicate = context -> {
if (exceedsMaxIterations(context.applicationContext(), retryConfig)) {
return false;
}
// 异常判断
for (Class<? extends Throwable> clazz : Exceptions()) {
if (clazz.ption())) {
return true;
}
}
return false;
};
// 使⽤reactor extra的retry组件
exceptionRetry = lyIf(retryContextPredicate)
.doOnRetry(context -> reset(context.applicationContext()))
.Retries());
}
return apply(statusCodeRepeat, exceptionRetry);
}
public boolean exceedsMaxIterations(ServerWebExchange exchange, RetryConfig retryConfig) {
Integer iteration = Attribute(RETRY_ITERATION_KEY);
//TODO: deal with null iteration
return iteration != null && iteration >= Retries();
}
public void reset(ServerWebExchange exchange) {
//TODO: what else to do to reset SWE?
public GatewayFilter apply(Repeat<ServerWebExchange> repeat, Retry<ServerWebExchange> retry) { return (exchange, chain) -> {
if (log.isTraceEnabled()) {
}
// chain.filter returns a Mono<Void>
Publisher<Void> publisher = chain.filter(exchange)
//.log("retry-filter", Level.INFO)
.doOnSuccessOrError((aVoid, throwable) -> {
// 获取已经重试的次数,默认值为-1
int iteration = AttributeOrDefault(RETRY_ITERATION_KEY, -1);
// 增加重试次数
});
if (retry != null) {
// retryWhen returns a Mono<Void>
// retry needs to go before repeat
publisher = ((Mono<Void>)publisher).retryWhen(retry.withApplicationContext(exchange));
}
if (repeat != null) {
// repeatWhen returns a Flux<Void>
// so this needs to be last and the variable a Publisher<Void>
publisher = ((Mono<Void>)publisher).repeatWhen(repeat.withApplicationContext(exchange));
}
return Mono.fromDirect(publisher);
};
}
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论