使用 guava-retrying
- 使用
guava-retrying
和okhttp
来演示下重试机制别和我说okhttp
自带重试机制,我不听
如何实现?
提供一个
retryer
对象和一个callable
接口即可提供一个接口,前两次请求会返回
404
,后面将返回200
- 提供一个服务,会调用该接口
- 如果发现调用出现
404
,会启动重试机制
- 如果发现调用出现
就这么简单
simple coding
依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- guava-retrying -->
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
<!-- okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.3.1</version>
</dependency>
OkHttpUtil
- 简单封装下
OkHttpClient
单独一个
OkHttpClient
实例内部会维护线程池的,所以单例化就好/**
* OkHttp 工具类
*/
@Slf4j
public class OkHttpUtil {
private OkHttpUtil() {
}
//////////////////////////////////////////////////////////////////////////
// OkHttpClient Singleton
//////////////////////////////////////////////////////////////////////////
public static OkHttpClient getOKHttpClientInstance() {
return LazyLoad.client;
}
private static class LazyLoad {
private static final OkHttpClient client = new OkHttpClient();
}
//////////////////////////////////////////////////////////////////////////
// util method
//////////////////////////////////////////////////////////////////////////
/**
* <h2>执行post请求,注意要求目标接口返回响应头具有application/json </h2>
*
* @param url 请求地址
* @param param 携带的参数实体类
* @param headersParam 请求头参数
* @param <T> 实体类类型
* @return {@link Response} 响应
* @throws IOException 执行请求时可能出现的异常
*/
public static <T> Response doPost(String url, T param, Map<String, String> headersParam) throws IOException {
MediaType contentType = MediaType.get(org.springframework.http.MediaType.APPLICATION_JSON_VALUE);
RequestBody requestBody = RequestBody.create(JSON.toJSONString(param), contentType);
// 构建 headers
Headers headers = _buildHeaders(headersParam);
// 构建 request
Request request = new Request.Builder()
.headers(headers)
.url(url)
.post(requestBody)
.build();
// 执行请求, 直接返回即可
return getOKHttpClientInstance().newCall(request).execute();
// sb了, 居然直接关掉流了。导致出现了 java.lang.IllegalStateException: closed
// try (Response response = getOKHttpClientInstance().newCall(request).execute()) {
// return response;
// } catch (IOException e) {
// log.error("请求url: {}时候发生了错误: {}", url, e.getLocalizedMessage(), e);
// throw e;
// }
}
/**
* 填充 header 为 headers
*
* @param headersParam {@link Map} headers 头
* @return {@link Headers} okHttp 的 headers 头
*/
private static Headers _buildHeaders(Map<String, String> headersParam) {
Headers.Builder headerBuilder = new Headers.Builder();
for (Map.Entry<String, String> entry : headersParam.entrySet()) {
headerBuilder.add(entry.getKey(), entry.getValue());
}
return headerBuilder.build();
}
}
vo
- 参数实体
/**
* 为了简单就不做校验了
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserVO {
private String name;
private Integer age;
}
Democontroller
总之就是前两次请求会返回
404
,后续都是200
@Slf4j
@RestController
@RequestMapping("/test")
public class DemoController {
private static AtomicInteger count = new AtomicInteger(0);
@RequestMapping(value = "/getData", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE)
public void getData(@RequestBody UserVO userVO, @RequestHeader String token, HttpServletResponse response) throws IOException {
// 超过 2次 返回成功
count.getAndIncrement();
log.info("当前count: {}", count.get());
if (count.get() > 2) {
response.setContentType("application/json;charset=utf-8");
response.getOutputStream().write("呵呵哒".getBytes(StandardCharsets.UTF_8));
// 该方式会直接设置 Content-Type 为 application/json;charset=ISO-8859-1
// response.getWriter().write("呵呵哒");
response.setStatus(HttpStatus.OK.value());
return;
}
System.out.println(userVO);
System.out.println(token);
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
}
RetryService
- 需要一个
Retryer
重试器,定义如何重试的时机- 可以添加一个
RetryListener
重试监听器,用于进行补偿策略或者紧急提示
- 可以添加一个
执行重试,即
retryer.call(callable)
, 传入一个具有重试业务的Callable
```java @Slf4j @Service public class RetryService { private static final int RETRY_TIMES = 3; /**重试监听器逻辑 */ private static RetryListener retryListener = new RetryListener() { @Override public
void onRetry(Attempt attempt) { Long retryTimes = attempt.getAttemptNumber();
log.info("重试时,监听器执行的逻辑,当前是第 {} 次重试", retryTimes);
if (retryTimes == RETRY_TIMES) {
log.error("重试次数已到,建议执行补偿策略");
// TODO 补偿策略
}
} };
// 重试器 private static Retryer
retryer = RetryerBuilder. newBuilder() // 当返回结果为 NULL 时,执行重试
.retryIfResult(Predicates.isNull())
// 当抛出 RuntimeException 时,执行重试
.retryIfRuntimeException()
// 自定义当抛出哪种异常时,执行重试
.retryIfExceptionOfType(IOException.class)
// 2s 后执行重试
.withWaitStrategy(WaitStrategies.fixedWait(2L, TimeUnit.SECONDS))
// 重试三次后不再重试 (会抛出异常)
// com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts.
.withStopStrategy(StopStrategies.stopAfterAttempt(RETRY_TIMES))
// 添加监听器
.withRetryListener(retryListener)
.build();
/**
* 开始执行重试
*
* @param callable
*/
public static void startRetry(Callable callable) {
try {
retryer.call(callable);
} catch (Exception e) {
log.error("执行任务时出现异常: {}", e.getLocalizedMessage(), e);
}
}
}
BusinessService<br />
- 有个请求上述接口的服务,如果出现 `404` 就开始执行重试策略
```java
public interface BusinessService {
public String postSomething(UserVO userVO, boolean isRetry) throws IOException;
}
@Slf4j
@Service
public class BusinessServiceImpl implements BusinessService {
private static final String url = "http://127.0.0.1:8080/test/getData";
private static final String token = "randomToken";
/**
* post 目标地址
*
* @param userVO 要发送的值
* @param isRetry 当前是否为重试阶段,首次调用应该设置为 false
* @throws IOException HttpClient 发送请求时候报错
*/
public String postSomething(UserVO userVO, boolean isRetry) throws IOException {
// TODO: check userVO
Map<String, String> headers = new HashMap<>(1);
headers.put("token", "randomToken");
// 注意用 try resource 对 response 进行关闭
try (Response response = OkHttpUtil.doPost(url, userVO, headers)) {
if (response.code() == 404) {
// 当前非重试阶段,才可调用重试,避免重复调用重试
if (!isRetry) {
doPostRetry(userVO);
}
return null;
}
// 调用 ResponseBody#string() 内部会对 response 进行 close
// https://blog.csdn.net/my_truelove/article/details/80133556
String body = response.body().string();
log.warn("成功时的值为: {}", body);
return body;
} catch (IOException e) {
log.error("请求url: {}时候发生了错误: {}", url, e.getLocalizedMessage(), e);
throw e;
}
}
/**
* 真正执行重试逻辑
*
* @param userVo
*/
public void doPostRetry(UserVO userVo) {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return postSomething(userVo, true);
}
};
RetryService.startRetry(callable);
}
}
调用
@Autowired
private BusinessService businessService;
@Test
public void testVA() throws IOException {
UserVO vo = new UserVO();
vo.setName("akarin");
vo.setAge(20);
businessService.postSomething(vo, false);
}
测试
- 先启动接口
-
打印
接口打印
2020-01-15 10:47:06.381 INFO 6324 --- [nio-8080-exec-1] c.e.redisdemo.controller.DemoController : 当前count: 1
UserVO(name=akarin, age=20)
randomToken
2020-01-15 10:47:06.397 INFO 6324 --- [nio-8080-exec-2] c.e.redisdemo.controller.DemoController : 当前count: 2
UserVO(name=akarin, age=20)
randomToken
2020-01-15 10:47:08.408 INFO 6324 --- [nio-8080-exec-3] c.e.redisdemo.controller.DemoController : 当前count: 3
测试打印
- 重试调用两次,在第二次重试调用成功