Java面向容錯程式設計之重試機制
來源:阿里雲開發者
阿里妹導讀
1.異常處理:透過捕獲和處理異常來避免應用程式崩潰。
2.錯誤處理:透過檢查錯誤程式碼並採取適當的措施,如重試或回滾,來處理錯誤。
3.重試機制:在出現錯誤時,嘗試重新執行程式碼塊,直到成功或達到最大嘗試次數。
4.備份機制:在主要系統出現故障時,切換到備用系統以保持應用程式的正常執行。
一、為什麼需要重試
二、如何重試
2.1 簡單重試方法
測試demo
@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);
}
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
@Test 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
@Test 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;
HSFConsumer超時重試原理
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
POM依賴
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId></dependency>
啟用@Retryable
"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");
}
service實現類新增@Retryable註解
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通知觸發這個回撥方法)。
注意事項:
@Recover註解來開啟重試失敗後呼叫的方法,此註解註釋的方法引數一定要是@Retryable丟擲的異常,否則無法識別。 @Recover標註的方法的返回值必須與@Retryable標註的方法一致。 該回撥方法與重試方法寫在同一個實現類裡面。 由於是基於AOP實現,所以不支援類裡自呼叫方法。 方法內不能使用try catch,只能往外拋異常,而且異常必須是Throwable型別的。
原理
缺陷
2.6 Guava Retrying
POM依賴
<dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> </dependency>
測試demo
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方法在返回值的時候進行重試。
優勢
三、優雅重試共性和原理
四、總結
來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024923/viewspace-3003425/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java程式設計解密-Dubbo負載均衡與叢集容錯機制Java程式設計解密負載
- Flink的狀態程式設計和容錯機制(四)程式設計
- Java併發程式設計之鎖機制之AQSJava程式設計AQS
- Java併發程式設計之鎖機制之Condition介面Java程式設計
- Java併發程式設計之鎖機制之LockSupport工具Java程式設計
- Java併發程式設計之鎖機制之Lock介面Java程式設計
- Java併發程式設計之鎖機制之(ReentrantLock)重入鎖Java程式設計ReentrantLock
- Java併發程式設計之鎖機制之引導篇Java程式設計
- 好程式設計師Java學習路線分享Java面試題之載入機制程式設計師Java面試題
- Java併發程式設計之鎖機制之ReentrantReadWriteLock(讀寫鎖)Java程式設計
- Spring之AOP面向切面程式設計Spring程式設計
- 【Spark】Spark容錯機制Spark
- Java中的面向切面程式設計(AOP)Java程式設計
- Java程式設計技術之淺析SPI服務發現機制Java程式設計
- java併發程式設計系列:wait/notify機制Java程式設計AI
- RabbitMQ重試機制MQ
- 高可用高可靠系統設計中的重試機制
- Spark Streaming 的容錯機制Spark
- 重試/retrying/retry/重試控制機制
- go 模仿JAVA,面向介面/鏈式程式設計GoJava程式設計
- 程式設計思想 面向切面程式設計程式設計
- 使用Java和Spring Retry實現重試機制JavaSpring
- JAVA重試機制多種方式深入淺出Java
- 面向介面程式設計程式設計
- Spring Retry重試機制Spring
- 深入解析Apache DolphinScheduler容錯機制Apache
- openGauss Copy介面支援容錯機制
- openGauss/MOGDB Copy支援容錯機制
- Java面向資料程式設計1.1版本Java程式設計
- 好程式設計師Java教程Java動態代理機制詳解程式設計師Java
- Java併發程式設計中的鎖機制詳解Java程式設計
- 好程式設計師Java分享JVM類載入機制程式設計師JavaJVM
- 面向介面程式設計實踐之aspnetcoreapi的抽象程式設計NetCoreAPI抽象
- [原始碼解析] 並行分散式框架 Celery 之 容錯機制原始碼並行分散式框架
- AOP(面向切面程式設計)程式設計
- AOP 面向切面程式設計程式設計
- 面向架構程式設計架構程式設計
- 好程式設計師Java教程分享Java面試題之Hibernate程式設計師Java面試題