使用 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 工具类*/@Slf4jpublic 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);// 构建 headersHeaders headers = _buildHeaders(headersParam);// 构建 requestRequest 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@Datapublic 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` 就开始执行重试策略```javapublic interface BusinessService {public String postSomething(UserVO userVO, boolean isRetry) throws IOException;}
@Slf4j@Servicepublic 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 userVOMap<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/80133556String 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>() {@Overridepublic String call() throws Exception {return postSomething(userVo, true);}};RetryService.startRetry(callable);}}
调用
@Autowiredprivate BusinessService businessService;@Testpublic 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: 1UserVO(name=akarin, age=20)randomToken2020-01-15 10:47:06.397 INFO 6324 --- [nio-8080-exec-2] c.e.redisdemo.controller.DemoController : 当前count: 2UserVO(name=akarin, age=20)randomToken2020-01-15 10:47:08.408 INFO 6324 --- [nio-8080-exec-3] c.e.redisdemo.controller.DemoController : 当前count: 3
测试打印

- 重试调用两次,在第二次重试调用成功
