为什么要做接口重试?
业务系统服务调用第三方系统的接口,以及第三方系统调用业务系统的服务时,当出现网络异常或超时导致的失败,需要进行重试,确保接口的可靠性
重试机制:
- 不允许重试:只允许调用重试回调一次
- 不限次数重试:允许无限重试,直到成功,此方式逻辑不当会导致死循环
- 固定次数重试:默认重试最大次数为3次,默认使用的策略
- 超时时间重试策略:默认超时时间为1秒,在指定的超时时间内允许重试
- 异常重试:针对不同异常的设置不同的重试策略,类似组合重试策略
最佳实践–SpringRetry
- 添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
-
启动类添加注解(@EnableRetry),开启重试机制
-
新增重试类
@Service
public class RetryServiceImpl {
private static Logger logger = LoggerFactory.getLogger(RetryServiceImpl.class);
private static RestTemplate restTemplate;
@Autowired
public RetryServiceImpl(RestTemplate restTemplate) {
RetryServiceImpl.restTemplate = restTemplate;
}
@Retryable(value = {ConnectTimeoutException.class, ResourceAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 2000))
public String doPostJsonNotToken(String url, String json) {
String resultString = "";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/json; charset=UTF-8"));
HttpEntity<String> formEntity = new HttpEntity<>(json, headers);
resultString = restTemplate.postForObject(url, formEntity, String.class);
String logInfo = String.format("调用接口:%s,参数:%s,返回结果%s",url, JSON.toJSONString(json),resultString);
logger.info(logInfo);
return resultString;
}
@Recover
public String recoverConnect(ConnectTimeoutException e) {
logger.error("重试机制3次已完成,依然连接超时..",e);
return "";
}
@Recover
public String recoverRead(ResourceAccessException e) {
logger.error("重试机制3次已完成,依然读取超时..",e);
return "";
}
}
当其他地方调用这个接口,发生网络抖动、读取资源超时,会自动重试三次.
@Retryable(value = {ConnectTimeoutException.class, ResourceAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 2000))
说一说上面的两个异常ConnectTimeoutException、ResourceAccessException 分别是连接异常和读取资源超时异常.分别对应RestTemplate设置的连接时间和读取资源时间,当超时是会报对应的异常.
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(10000);
factory.setReadTimeout(100000);
RestTemplate restTemplate = new RestTemplate(factory);
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
// 使用 utf-8 编码集的 conver 替换默认的 conver
// (默认的 string conver 的编码集为"ISO-8859-1")
messageConverters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
messageConverters.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}
注意:
如果你发现你的接口出现对应的异常,没有去重试时,需要注意Retryable方法只有直接通过代理对象调用时才会生效,通过其他方法间接调用不生效(所有基于AOP的注解都一样),工具类调用是不可以的.
温馨提示:在Spring中,只有需要用到Aop代理的类才会生成代理对象,不需要Aop的直接返回原生对象