前言

重试,我相信大家并不陌生。在我们调用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");
}
System.err.println(ac.addAndGet(1));
return new HouseInfo(1L, "上海", "虹口", "XX小区");
}

更多Spring Cloud代码尽在:https://github.com/yinjihuan/spring-cloud

源码欣赏

    @Override
public GatewayFilter apply(RetryConfig retryConfig) {
// 验证重试配置格式是否正确
retryConfig.validate(); Repeat<ServerWebExchange> statusCodeRepeat = null;
if (!retryConfig.getStatuses().isEmpty() || !retryConfig.getSeries().isEmpty()) {
Predicate<RepeatContext<ServerWebExchange>> repeatPredicate = context -> {
ServerWebExchange exchange = context.applicationContext();
// 判断重试次数是否已经达到了配置的最大值
if (exceedsMaxIterations(exchange, retryConfig)) {
return false;
}
// 获取响应的状态码
HttpStatus statusCode = exchange.getResponse().getStatusCode();
// 获取请求方法类型
HttpMethod httpMethod = exchange.getRequest().getMethod();
// 判断响应状态码是否在配置中存在
boolean retryableStatusCode = retryConfig.getStatuses().contains(statusCode); if (!retryableStatusCode && statusCode != null) { // null status code might mean a network exception?
// try the series
retryableStatusCode = retryConfig.getSeries().stream()
.anyMatch(series -> statusCode.series().equals(series));
}
// 判断方法是否包含在配置中
boolean retryableMethod = retryConfig.getMethods().contains(httpMethod);
// 决定是否要进行重试
return retryableMethod && retryableStatusCode;
}; statusCodeRepeat = Repeat.onlyIf(repeatPredicate)
.doOnRepeat(context -> reset(context.applicationContext()));
} //TODO: support timeout, backoff, jitter, etc... in Builder Retry<ServerWebExchange> exceptionRetry = null;
if (!retryConfig.getExceptions().isEmpty()) {
Predicate<RetryContext<ServerWebExchange>> retryContextPredicate = context -> {
if (exceedsMaxIterations(context.applicationContext(), retryConfig)) {
return false;
}
// 异常判断
for (Class<? extends Throwable> clazz : retryConfig.getExceptions()) {
if (clazz.isInstance(context.exception())) {
return true;
}
}
return false;
};
// 使用reactor extra的retry组件
exceptionRetry = Retry.onlyIf(retryContextPredicate)
.doOnRetry(context -> reset(context.applicationContext()))
.retryMax(retryConfig.getRetries());
} return apply(statusCodeRepeat, exceptionRetry);
} public boolean exceedsMaxIterations(ServerWebExchange exchange, RetryConfig retryConfig) {
Integer iteration = exchange.getAttribute(RETRY_ITERATION_KEY); //TODO: deal with null iteration
return iteration != null && iteration >= retryConfig.getRetries();
} public void reset(ServerWebExchange exchange) {
//TODO: what else to do to reset SWE?
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR);
} public GatewayFilter apply(Repeat<ServerWebExchange> repeat, Retry<ServerWebExchange> retry) {
return (exchange, chain) -> {
if (log.isTraceEnabled()) {
log.trace("Entering retry-filter");
} // chain.filter returns a Mono<Void>
Publisher<Void> publisher = chain.filter(exchange)
//.log("retry-filter", Level.INFO)
.doOnSuccessOrError((aVoid, throwable) -> {
// 获取已经重试的次数,默认值为-1
int iteration = exchange.getAttributeOrDefault(RETRY_ITERATION_KEY, -1);
// 增加重试次数
exchange.getAttributes().put(RETRY_ITERATION_KEY, iteration + 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);
};
}

欢迎加入我的知识星球,一起交流技术,免费学习猿天地的课程(http://cxytiandi.com/course)

Spring Cloud Gateway重试机制的更多相关文章

  1. Spring Cloud 请求重试机制核心代码分析

    场景 发布微服务的操作一般都是打完新代码的包,kill掉在跑的应用,替换新的包,启动. spring cloud 中使用eureka为注册中心,它是允许服务列表数据的延迟性的,就是说即使应用已经不在服 ...

  2. Spring Cloud学习 之 Spring Cloud Ribbon 重试机制及超时设置不生效

    今天测了一下Ribbon的重试跟超时机制,发现进行的全局超时配置一直不生效,配置如下: ribbon: #单位ms,请求连接的超时时间,默认1000 ConnectTimeout: 500 #单位ms ...

  3. 一文详解Spring Cloud Feign重试机制

    前言 Feign组件默认使用Ribbon的重试机制并增加了根据状态码判断重试机制,默认情况下是不启用的.Feign使用的是Spring Retry组件,需要引入依赖才能启用. 一.POM引入Sprin ...

  4. springcloud(十七):服务网关 Spring Cloud GateWay 熔断、限流、重试

    上篇文章介绍了 Gataway 和注册中心的使用,以及 Gataway 中 Filter 的基本使用,这篇文章我们将继续介绍 Filter 的一些常用功能. 修改请求路径的过滤器 StripPrefi ...

  5. 网关服务Spring Cloud Gateway(三)

    上篇文章介绍了 Gataway 和注册中心的使用,以及 Gataway 中 Filter 的基本使用,这篇文章我们将继续介绍 Filter 的一些常用功能. 修改请求路径的过滤器 StripPrefi ...

  6. 微服务网关实战——Spring Cloud Gateway

    导读 作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用.本文对Spring Clou ...

  7. 微服务网关 Spring Cloud Gateway

    1.  为什么是Spring Cloud Gateway 一句话,Spring Cloud已经放弃Netflix Zuul了.现在Spring Cloud中引用的还是Zuul 1.x版本,而这个版本是 ...

  8. 跟我学SpringCloud | 第十四篇:Spring Cloud Gateway高级应用

    SpringCloud系列教程 | 第十四篇:Spring Cloud Gateway高级应用 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 ...

  9. Spring Cloud gateway 网关服务二 断言、过滤器

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

随机推荐

  1. IDEA设置方法参数列表类型自动提示

    默认情况下,IDEA的提示不够完全,可以通过以下设置,将提示功能打开的更完善. 效果如下面俩图所示

  2. 时间复杂度o(1), o(n), o(logn), o(nlogn)

    1.时间复杂度o(1), o(n), o(logn), o(nlogn).算法时间复杂度的时候有说o(1), o(n), o(logn), o(nlogn),这是算法的时空复杂度的表示.不仅仅用于表示 ...

  3. @Valid 数据校验 + 自定义全局异常信息

    关于javax.validation.Validator校验的使用 对于要校验的实体类:其需要校验的字段上需要添加注解 实际例子 使用:首先要拿到 validator的子类 Validator val ...

  4. 绑定 Binding Path=.,Binding.,Binding Source={StaticResource ResourceKey="Hello"} xmlns:sys="clr-namespace:System;assembly=mscorlib"

    xmlns:sys="clr-namespace:System;assembly=mscorlib" <Window.Resources> <Style Targ ...

  5. Python - 条件控制、循环语句 - 第十二天

    Python 条件控制.循环语句 end 关键字 关键字end可以用于将结果输出到同一行,或者在输出的末尾添加不同的字符,实例如下: Python 条件语句是通过一条或多条语句的执行结果(True 或 ...

  6. 分布式Redis深度历险-Cluster

    本文为分布式Redis深度历险系列的第三篇,主要内容为Redis的Cluster,也就是Redis集群功能. Redis集群是Redis官方提供的分布式方案,整个集群通过将所有数据分成16384个槽来 ...

  7. jQuery遍历 - 过滤first(),last()和eq()使用

    jQuery遍历 - 过滤最基本的过滤方法是first(),last()和eq(),它们允许您根据元素在一组元素中的位置选择特定元素. 其他过滤方法(如filter()和not())允许您选择与特定条 ...

  8. js删除html标记 去掉所有html标记

    js删除html标记 去掉所有html标记 function delHtml(str){ return str.replace(/<[^>]+>/g,""); / ...

  9. React + Ts 实现三子棋小游戏

    在这里阅读效果更佳 还记得当年和同桌在草稿纸上下三子棋的时光吗 今天我们就用代码来重温一下年少(假设你有react基础,没有也行,只要你会三大框架的任意一种,上手react不难) 游戏规则 双方各执一 ...

  10. Mybatis插入实体类字段为关键字解决方案

    1. Mybatis插入实体类字段为关键字解决方案 1.1. 前言 可能你插入字段为关键字时报如下错误,且字段名不适合改变 You have an error in your SQL syntax; ...