Spring Cloud Gateway重试机制原理解析
重试,我相信大家并不陌生。在我们调用Http接口的时候,总会因为某种原因调用失败,这个时候我们可以通过重试的方式,来重新请求接口。
生活中这样的事例很多,比如打电话,对方正在通话中啊,信号不好啊等等原因,你总会打不通,当你第一次没打通之后,你会打第二次,第三次…第四次就通了。
重试也要注意应用场景,读数据的接口比较适合重试的场景,写数据的接口就需要注意接口的幂等性了。还有就是重试次数如果太多的话会导致请求量加倍,给后端造成更大的压力,设置合理的重试机制才是最关键的。
今天我们来简单的了解下SpringCloudGateway中的重试机制和使用。
使用讲解
RetryGatewayFilter是SpringCloudGateway对请求重试提供的一个GatewayFilterFactory。
配置方式:
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:
publicstaticclassRetryConfig{
privateintretries=3;
privateListseries=toList(Series.SERVER_ERROR);
privateListstatuses=newArrayList<>();
privateListmethods=toList(HttpMethod.GET);
privateList>exceptions=toList(IOException.class);
//.....
}
retries:重试次数,默认值是3次
series:状态码配置(分段),符合的某段状态码才会进行重试逻辑,默认值是SERVER_ERROR,值是5,也就是5XX(5开头的状态码),共有5个值:
publicenumSeries{
INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);
}
statuses:状态码配置,和series不同的是这边是具体状态码的配置,取值请参考:org.springframework.http.HttpStatus
methods:指定哪些方法的请求需要进行重试逻辑,默认值是GET方法,取值如下:
publicenumHttpMethod{
GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE;
}
exceptions:指定哪些异常需要进行重试逻辑,默认值是java.io.IOException
代码测试
就写个接口,在接口中记录请求次数,然后抛出一个异常模拟500,通过网关访问这个接口,如果你配置了重试次数是3,那么接口中会输出4次结果才是对的,证明重试生效了。
AtomicIntegerac=newAtomicInteger();
@GetMapping("/data")
publicHouseInfogetData(@RequestParam("name")Stringname){
if(StringUtils.isBlank(name)){
thrownewRuntimeException("error");
}
System.err.println(ac.addAndGet(1));
returnnewHouseInfo(1L,"上海","虹口","XX小区");
}
源码欣赏
@Override
publicGatewayFilterapply(RetryConfigretryConfig){
//验证重试配置格式是否正确
retryConfig.validate();
RepeatstatusCodeRepeat=null;
if(!retryConfig.getStatuses().isEmpty()||!retryConfig.getSeries().isEmpty()){
Predicate>repeatPredicate=context->{
ServerWebExchangeexchange=context.applicationContext();
//判断重试次数是否已经达到了配置的最大值
if(exceedsMaxIterations(exchange,retryConfig)){
returnfalse;
}
//获取响应的状态码
HttpStatusstatusCode=exchange.getResponse().getStatusCode();
//获取请求方法类型
HttpMethodhttpMethod=exchange.getRequest().getMethod();
//判断响应状态码是否在配置中存在
booleanretryableStatusCode=retryConfig.getStatuses().contains(statusCode);
if(!retryableStatusCode&&statusCode!=null){//nullstatuscodemightmeananetworkexception?
//trytheseries
retryableStatusCode=retryConfig.getSeries().stream()
.anyMatch(series->statusCode.series().equals(series));
}
//判断方法是否包含在配置中
booleanretryableMethod=retryConfig.getMethods().contains(httpMethod);
//决定是否要进行重试
returnretryableMethod&&retryableStatusCode;
};
statusCodeRepeat=Repeat.onlyIf(repeatPredicate)
.doOnRepeat(context->reset(context.applicationContext()));
}
//TODO:supporttimeout,backoff,jitter,etc...inBuilder
RetryexceptionRetry=null;
if(!retryConfig.getExceptions().isEmpty()){
Predicate>retryContextPredicate=context->{
if(exceedsMaxIterations(context.applicationContext(),retryConfig)){
returnfalse;
}
//异常判断
for(Classclazz:retryConfig.getExceptions()){
if(clazz.isInstance(context.exception())){
returntrue;
}
}
returnfalse;
};
//使用reactorextra的retry组件
exceptionRetry=Retry.onlyIf(retryContextPredicate)
.doOnRetry(context->reset(context.applicationContext()))
.retryMax(retryConfig.getRetries());
}
returnapply(statusCodeRepeat,exceptionRetry);
}
publicbooleanexceedsMaxIterations(ServerWebExchangeexchange,RetryConfigretryConfig){
Integeriteration=exchange.getAttribute(RETRY_ITERATION_KEY);
//TODO:dealwithnulliteration
returniteration!=null&&iteration>=retryConfig.getRetries();
}
publicvoidreset(ServerWebExchangeexchange){
//TODO:whatelsetodotoresetSWE?
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR);
}
publicGatewayFilterapply(Repeatrepeat,Retryretry){
return(exchange,chain)->{
if(log.isTraceEnabled()){
log.trace("Enteringretry-filter");
}
//chain.filterreturnsaMono
Publisherpublisher=chain.filter(exchange)
//.log("retry-filter",Level.INFO)
.doOnSuccessOrError((aVoid,throwable)->{
//获取已经重试的次数,默认值为-1
intiteration=exchange.getAttributeOrDefault(RETRY_ITERATION_KEY,-1);
//增加重试次数
exchange.getAttributes().put(RETRY_ITERATION_KEY,iteration+1);
});
if(retry!=null){
//retryWhenreturnsaMono
//retryneedstogobeforerepeat
publisher=((Mono)publisher).retryWhen(retry.withApplicationContext(exchange));
}
if(repeat!=null){
//repeatWhenreturnsaFlux
//sothisneedstobelastandthevariableaPublisher
publisher=((Mono)publisher).repeatWhen(repeat.withApplicationContext(exchange));
}
returnMono.fromDirect(publisher);
};
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。