目录
一般在各种业务场景中,为了保持系统稳定,我们都会有相应的重试机制,某个接口某个数据库链接由于网络抖动或者其他因素导致响应失败。
Guava-retrying或者分析过其源码你会发现,guava-retrying重试组件特别轻量级,核心类就那几个,并且使用简单设计优雅,但是它也存在缺点。
优点
- 策略丰富并且支持自定义
- 使用简单
- 设计优雅
缺点
- 不支持注解
- 侵入业务代码
- 重复性强
一、maven 依赖
<guava-retry.version>2.0.0</guava-retry.version>
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>${guava-retry.version}</version>
</dependency>
二、创建重试器并执行重试
import cn.meng.listener.RestTemplateRetryListener;
import cn.hutool.core.util.ObjectUtil;
import com.github.rholder.retry.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @Author: meng
* @Description: http
* @Date: 2022/8/28 09:35
* @Version: 1.0
*/
@Slf4j
@Service
public class HttpService {
@Resource
private RestTemplate httpsRestTemplate;
private Retryer<ResponseEntity<String>> retryer = RetryerBuilder
.<ResponseEntity<String>>newBuilder()
// retryIf 重试条件
.retryIfResult(Objects::isNull)
// 本次尝试失败后,过3秒再次尝试
.withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
// 总共尝试3次
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
// 遇到运行时异常才重试
.retryIfRuntimeException()
.withRetryListener(new RestTemplateRetryListener<>())
.build();
public ResponseEntity<String> exchange(String token, String url, String data) {
Callable<ResponseEntity<String>> getResponseEntity = new Callable<ResponseEntity<String>>() {
@Override
public ResponseEntity<String> call() throws Exception {
//构建headers
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(CommonConstant.HEADER_TOKEN, token);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> httpEntity = new HttpEntity<>(data, httpHeaders);
return httpsRestTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
}
};
ResponseEntity<String> responseEntity = null;
try {
responseEntity = retryer.call(getResponseEntity);
} catch (RetryException | ExecutionException e) {
e.printStackTrace();
}
if(ObjectUtil.isNull(responseEntity)) {
throw new RuntimeException("调用服务网络异常");
}
return responseEntity;
}
}
三、重试监听
import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryListener;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: meng
* @Description: 重试监听
* @Date: 2022/10/10 19:22
* @Version: 1.0
*/
@Slf4j
public class RestTemplateRetryListener<ResponseEntity> implements RetryListener {
@Override
public <ResponseEntity> void onRetry(Attempt<ResponseEntity> attempt) {
// 第几次重试;距离第一次重试的延迟;重试结果: 是异常终止, 还是正常返回
log.info("[retry]time=" + attempt.getAttemptNumber() + ",delay=" + attempt.getDelaySinceFirstAttempt()
+ ",hasException=" + attempt.hasException() + ",hasResult=" + attempt.hasResult());
// 是什么原因导致异常
if (attempt.hasException()) {
log.error("RestTemplate,causeBy=" + attempt.getExceptionCause().toString());
}
}
}
Attemp
Attemp既是一次任务重试(call),也是一次请求的结果,记录了当前请求的重试次数,是否包含异常和请求的返回值。我们可以配合监听器使用,用于记录重试过程的细节,常用的方法有如下几个:
- getAttemptNumber(),表示准备开始第几次重试;
- getDelaySinceFirstAttempt(),表示距离第一次重试的延迟,也就是与第一次重试的时间差,单位毫秒;
- hasException(),表示是异常导致的重试还是正常返回;
- hasResult(),表示是否返回了结果;因为有时候是因为返回了特定结果才进行重试;
- getExceptionCause(),如果是异常导致的重试,那么获取具体具体的异常类型;
- getResult(),返回重试的结果;
- get(),如果有的话,返回重试的结果;和getResult不同的在于对异常的处理;