阿里妹导读
1.异常处理:通过捕获和处理异常来避免应用程序崩溃。
2.错误处理:通过检查错误代码并采取适当的措施,如重试或回滚,来处理错误。
3.重试机制:在出现错误时,尝试重新执行代码块,直到成功或达到最大尝试次数。
4.备份机制:在主要系统出现故障时,切换到备用系统以保持应用程序的正常运行。
一、为什么需要重试
二、如何重试
2.1 简单重试方法
@Test
public 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);
}
public 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
public Integer V2Retry(int code) {
RetryableTestServiceImpl realService = new RetryableTestServiceImpl();
RetryableTesterviceImpl proxyService = (RetryableTestServiceImpl) DynamicProxyTest.getProxy(realService);
proxyService.retryableTest(code);
}
2.3 字节码技术 生成代理重试
使用方式
public class CglibProxyTest implements MethodInterceptor {
public 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
public 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) {
//broadcast
return broadcast(invocation, future);
} else if (consumerMethodModel.getExecuteTimes() > 1) {
//retry
return retry(invocation, invocationHandler, future, consumerMethodModel.getExecuteTimes());
} else {
//normal
return getRPCResult(invocation, future);
}
} else {
// pseudo response, should be ignored
HSFRequest 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 timeout
if (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>
"me.ele.camp"},excludeName = {"me.ele.oc.orm.OcOrmAutoConfiguraion"}) (scanBasePackages = {
"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");
}
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;
}
public 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>() {
public Boolean call() throws Exception {
// do something useful here
log.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 {
@Override
public <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 个优质回答分别获得阿里云开发者无线充电器一个。