
阿里妹导读
1.异常处理:通过捕获和处理异常来避免应用程序崩溃。
2.错误处理:通过检查错误代码并采取适当的措施,如重试或回滚,来处理错误。
3.重试机制:在出现错误时,尝试重新执行代码块,直到成功或达到最大尝试次数。
4.备份机制:在主要系统出现故障时,切换到备用系统以保持应用程序的正常运行。
一、为什么需要重试

二、如何重试
2.1 简单重试方法
@Testpublic Integer sampleRetry(int code) {System.out.println("sampleRetry,时间:" + LocalTime.now());int times = 0;while (times < MAX_TIMES) {try {postCommentsService.retryableTest(code);} catch (Exception e) {times++;System.out.println("重试次数" + times);if (times >= MAX_TIMES) {//记录落库,后续定时任务兜底重试//do something record...throw new RuntimeException(e);}}}System.out.println("sampleRetry,返回!");return null;}
2.2 动态代理模式版本
使用方式
public class DynamicProxyTest implements InvocationHandler {private final Object subject;public DynamicProxy(Object subject) {this.subject = subject;}/*** 获取动态代理** @param realSubject 代理对象*/public static Object getProxy(Object realSubject) {// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的InvocationHandler handler = new DynamicProxy(realSubject);return Proxy.newProxyInstance(handler.getClass().getClassLoader(),realSubject.getClass().getInterfaces(), handler);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {int times = 0;while (times < MAX_TIMES) {try {// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用return method.invoke(subject, args);} catch (Exception e) {times++;System.out.println("重试次数" + times);if (times >= MAX_TIMES) {//记录落库,后续定时任务兜底重试//do something record...throw new RuntimeException(e);}}}return null;}}
测试demo
@Testpublic Integer V2Retry(int code) {RetryableTestServiceImpl realService = new RetryableTestServiceImpl();RetryableTesterviceImpl proxyService = (RetryableTestServiceImpl) DynamicProxyTest.getProxy(realService);proxyService.retryableTest(code);}
2.3 字节码技术 生成代理重试
使用方式
public class CglibProxyTest implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {int times = 0;while (times < MAX_TIMES) {try {//通过代理子类调用父类的方法return methodProxy.invokeSuper(o, objects);} catch (Exception e) {times++;if (times >= MAX_TIMES) {throw new RuntimeException(e);}}}return null;}/*** 获取代理类* @param clazz 类信息* @return 代理类结果*/public Object getProxy(Class clazz){Enhancer enhancer = new Enhancer();//目标对象类enhancer.setSuperclass(clazz);enhancer.setCallback(this);//通过字节码技术创建目标对象类的子类实例作为代理return enhancer.create();}}
测试demo
@Testpublic Integer CglibRetry(int code) {RetryableTestServiceImpl proxyService = (RetryableTestServiceImpl) new CglibProxyTest().getProxy(RetryableTestServiceImpl.class);proxyService.retryableTest(code);}
2.4 HSF调用超时重试
@HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "hsf",clientTimeout = 2000, methodSpecials = {@ConsumerMethodSpecial(methodName = "methodA", clientTimeout = "100", retries = "2"),@ConsumerMethodSpecial(methodName = "methodB", clientTimeout = "200", retries = "1")})private XxxHSFService xxxHSFServiceConsumer;
private RPCResult invokeType(Invocation invocation, InvocationHandler invocationHandler) throws Throwable {final ConsumerMethodModel consumerMethodModel = invocation.getClientInvocationContext().getMethodModel();String methodName = consumerMethodModel.getMethodName(invocation.getHsfRequest());final InvokeMode invokeType = getInvokeType(consumerMethodModel.getMetadata(), methodName);invocation.setInvokeType(invokeType);ListenableFuture<RPCResult> future = invocationHandler.invoke(invocation);if (InvokeMode.SYNC == invokeType) {if (invocation.getBroadcastFutures() != null && invocation.getBroadcastFutures().size() > 1) {//broadcastreturn broadcast(invocation, future);} else if (consumerMethodModel.getExecuteTimes() > 1) {//retryreturn retry(invocation, invocationHandler, future, consumerMethodModel.getExecuteTimes());} else {//normalreturn getRPCResult(invocation, future);}} else {// pseudo response, should be ignoredHSFRequest request = invocation.getHsfRequest();Object appResponse = null;if (request.getReturnClass() != null) {appResponse = ReflectUtils.defaultReturn(request.getReturnClass());}HSFResponse hsfResponse = new HSFResponse();hsfResponse.setAppResponse(appResponse);RPCResult rpcResult = new RPCResult();rpcResult.setHsfResponse(hsfResponse);return rpcResult;}}
private RPCResult retry(Invocation invocation, InvocationHandler invocationHandler,ListenableFuture<RPCResult> future, int executeTimes) throws Throwable {int retryTime = 0;while (true) {retryTime++;if (retryTime > 1) {future = invocationHandler.invoke(invocation);}int timeout = -1;try {timeout = (int) invocation.getInvokerContext().getTimeout();RPCResult rpcResult = future.get(timeout, TimeUnit.MILLISECONDS);return rpcResult;} catch (ExecutionException e) {throw new HSFTimeOutException(getErrorLog(e.getMessage()), e);} catch (TimeoutException e) {//retry only when timeoutif (retryTime < executeTimes) {continue;} else {throw new HSFTimeOutException(getErrorLog(e.getMessage()), timeout + "", e);}} catch (Throwable e) {throw new HSFException("", e);}}}
1、只有方法被同步调用时候才会发生重试。
2、只有hsf接口出现TimeoutException才会调用重试方法。
2.5 Spring Retry
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></dependency>
@EnableRetry@SpringBootApplication(scanBasePackages = {"me.ele.camp"},excludeName = {"me.ele.oc.orm.OcOrmAutoConfiguraion"})@ImportResource({"classpath*:sentinel-tracer.xml"})public class Application {public static void main(String[] args) {System.setProperty("APPID","alsc-info-local-camp");System.setProperty("project.name","alsc-info-local-camp");}
@Override@Retryable(value = BizException.class, maxAttempts = 6)public Integer retryableTest(Integer code) {System.out.println("retryableTest,时间:" + LocalTime.now());if (code == 0) {throw new BizException("异常", "异常");}BaseResponse<Object> objectBaseResponse = ResponseHandler.serviceFailure(ResponseErrorEnum.UPDATE_COMMENT_FAILURE);System.out.println("retryableTest,正确!");return 200;}@Recoverpublic Integer recover(BizException e) {System.out.println("回调方法执行!!!!");//记日志到数据库 或者调用其余的方法return 404;};
可以看到代码里面,实现方法上面加上了注解 @Retryable,@Retryable有以下参数可以配置:
Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。可以看到传参里面写的是 BizException e,这个是作为回调的接头暗号(重试次数用完了,还是失败,我们抛出这个BizException e通知触发这个回调方法)。

2.6 Guava Retrying
<dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>2.0.0</version></dependency>
public static void main(String[] args) {Callable<Boolean> callable = new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {// do something useful herelog.info("call...");throw new RuntimeException();}};Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()//retryIf 重试条件.retryIfException().retryIfRuntimeException().retryIfExceptionOfType(Exception.class).retryIfException(Predicates.equalTo(new Exception())).retryIfResult(Predicates.equalTo(false))//等待策略:每次请求间隔1s.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))//停止策略 : 尝试请求6次.withStopStrategy(StopStrategies.stopAfterAttempt(6))//时间限制 : 某次请求不得超过2s.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))//注册一个自定义监听器(可以实现失败后的兜底方法).withRetryListener(new MyRetryListener()).build();try {retryer.call(callable);} catch (Exception ee) {ee.printStackTrace();}}
public class MyRetryListener implements RetryListener {@Overridepublic <V> void onRetry(Attempt<V> attempt) {// 第几次重试System.out.print("[retry]time=" + attempt.getAttemptNumber());// 距离第一次重试的延迟System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());// 重试结果: 是异常终止, 还是正常返回System.out.print(",hasException=" + attempt.hasException());System.out.print(",hasResult=" + attempt.hasResult());// 是什么原因导致异常if (attempt.hasException()) {System.out.print(",causeBy=" + attempt.getExceptionCause().toString());// do something useful here} else {// 正常返回时的结果System.out.print(",result=" + attempt.getResult());}System.out.println();}}
RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。
retryIfException,抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。
retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException都属于runtime异常,也包括自定义的error。
retryIfResult可以指定你的Callable方法在返回值的时候进行重试。
三、优雅重试共性和原理
四、总结
参与话题讨论赢礼品
你时常焦虑吗?一般是在什么场景,工作或生活?我们是否掉入了“别人贩卖的焦虑”(PUA、35岁危机)的陷阱?
点击阅读原文参加话题讨论,截止2024年1月14日24时,我们将会选出 2 名幸运用户和 2 个优质回答分别获得阿里云开发者无线充电器一个。