1. 理解重試機制spring
6. 模板方法設計模式實現異步重試機制springboot
若是有,請轉給我!服務器
「重試是爲了提升成功的可能性「架構
反過來理解,任何可能失敗且容許重試操做的場景,就適合使用重試機制。但有了重試機制就必定能成功嗎?顯然不是。若是不成功就一直重試,這種處理方式會使得業務線程一直被重試佔用,這樣會致使服務的負載線程暴增直至服務宕機,所以須要限制重試次數。失敗狀況下,咱們須要作後續的操做,若是是數據庫操做的重試,須要回滾事物;若是是服務調用的重試,須要郵件報警通知運維開發人員,恢復服務。併發
對於服務接口調用,多是由於網絡波動致使超時失敗,這時候全部重試次數是在很短期內發起的話,就很容易所有超時失敗,所以超時機制還須要引入重試動做之間時間間隔以及第一次失敗後延遲多長時間再開始重試等機制。
重試機制要素
任何可能失敗且容許重試操做的場景,就適合使用重試機制。那麼在分佈式系統開發環境中,哪些場景須要是使用重試機制呢。
spring-retry核心:配置重試元數據,失敗恢復或報警通知。
pom文件依賴
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
配置重試元數據
@Override @Retryable(value = Exception.class,maxAttempts = 3 , backoff = @Backoff(delay = 2000,multiplier = 1.5)) public int retryServiceOne(int code) throws Exception { // TODO Auto-generated method stub System.out.println("retryServiceOne被調用,時間:"+LocalTime.now()); System.out.println("執行當前業務邏輯的線程名:"+Thread.currentThread().getName()); if (code==0){ throw new Exception("業務執行失敗狀況!"); } System.out.println("retryServiceOne執行成功!"); return 200; }
配置元數據狀況:
測試:
啓動應用,瀏覽器輸入:http://localhost:8080/springRetry。
後臺結果:
執行業務發起邏輯的線程名:http-nio-8080-exec-6 retryServiceOne被調用,時間:17:55:48.235 執行當前業務邏輯的線程名:http-nio-8080-exec-6 retryServiceOne被調用,時間:17:55:50.235 執行當前業務邏輯的線程名:http-nio-8080-exec-6 retryServiceOne被調用,時間:17:55:53.236 執行當前業務邏輯的線程名:http-nio-8080-exec-6 回調方法執行!!!!
註解類:
/** * 重試註解 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface JdkRetry{ //默認 int maxAttempts() default 3; //默認每次間隔等待3000毫秒 long waitTime() default 3000; //捕捉到的異常類型 再進行重發 Class<?> exception () default Exception.class ; String recoverServiceName () default "DefaultRecoverImpl"; }
註解類包含的元數據有:
使用spring AOP技術,實現重試註解的切面邏輯類RetryAspect。
@Transactional(rollbackFor = Exception.class) @Around("@annotation(jdkRetry)") //開發自定義註解的時候,定要注意 @annotation(jdkRetry)和下面方法的參數,按規定是固定的形式的,不然報錯 public Object doConcurrentOperation(ProceedingJoinPoint pjp , JdkRetry jdkRetry) throws Throwable { //獲取註解的屬性 // pjp.getClass().getMethod(, parameterTypes) System.out.println("切面做用:"+jdkRetry.maxAttempts()+ " 恢復策略類:"+ jdkRetry.recoverServiceName()); Object service = JdkApplicationContext.jdkApplicationContext.getBean(jdkRetry.recoverServiceName()); Recover recover = null; if(service == null) return new Exception("recover處理服務實例不存在"); recover = (Recover)service; long waitTime = jdkRetry.waitTime(); maxRetries = jdkRetry.maxAttempts(); Class<?> exceptionClass = jdkRetry.exception(); int numAttempts = 0; do { numAttempts++; try { //再次執行業務代碼 return pjp.proceed(); } catch (Exception ex) { //必須只是樂觀鎖更新才能進行重試邏輯 System.out.println(ex.getClass().getName()); if(!ex.getClass().getName().equals(exceptionClass.getName())) throw ex; if (numAttempts > maxRetries) { recover.recover(null); //log failure information, and throw exception // 若是大於 默認的重試機制 次數,咱們這回就真正的拋出去了 // throw new Exception("重試邏輯執行完成,業務仍是失敗!"); }else{ //若是 沒達到最大的重試次數,將再次執行 System.out.println("=====正在重試====="+numAttempts+"次"); TimeUnit.MILLISECONDS.sleep(waitTime); } } } while (numAttempts <= this.maxRetries); return 500; }
切面類獲取到重試註解元信息後,切面邏輯會作如下相應的處理:
測試:
啓動應用,瀏覽器輸入:http://localhost:8080/testAnnotationRetry
結果:
切面做用:3 恢復策略類:DefaultRecoverImpl AnnotationServiceImpl被調用,時間:18:11:25.748 org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException =====正在重試=====1次 AnnotationServiceImpl被調用,時間:18:11:28.748 org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException =====正在重試=====2次 AnnotationServiceImpl被調用,時間:18:11:31.749 org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException =====正在重試=====3次 AnnotationServiceImpl被調用,時間:18:11:34.749 org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException 2020-05-26 18:11:34.749 ERROR 14892 --- [io-8080-exec-10] o.j.r.j.recover.impl.DefaultRecoverImpl : 重試失敗,未進行任何補全,此爲默認補全:打出錯誤日誌
冪等性問題:
在分佈式架構下,服務之間調用會由於網絡緣由出現超時失敗狀況,而重試機制會重複屢次調用服務,可是對於被調用放,就可能收到了屢次調用。若是被調用方不具備天生的冪等性,那就須要增長服務調用的判重模塊,並對每次調用都添加一個惟一的id。
大量請求超時堆積:
超高併發下,大量的請求若是都進行超時重試的話,若是你的重試時間設置不安全的話,會致使大量的請求佔用服務器線程進行重試,這時候服務器線程負載就會暴增,致使服務器宕機。對於這種超高併發下的重試設計,咱們不能讓重試放在業務線程,而是統一由異步任務來執行。
模板方法設計模式來實現異步重試機制
全部業務類繼承重試模板類RetryTemplate
@Service("serviceone") public class RetryTemplateImpl extends RetryTemplate{ public RetryTemplateImpl() { // TODO Auto-generated constructor stub this.setRecover(new RecoverImpl()); } @Override protected Object doBiz() throws Exception { // TODO Auto-generated method stub int code = 0; System.out.println("RetryTemplateImpl被調用,時間:"+LocalTime.now()); if (code==0){ throw new Exception("業務執行失敗狀況!"); } System.out.println("RetryTemplateImpl執行成功!"); return 200; } class RecoverImpl implements Recover{ @Override public String recover() { // TODO Auto-generated method stub System.out.println("重試失敗 恢復邏輯,記錄日誌等操做"); return null; } } }
測試:
啓動應用,瀏覽器輸入:http://localhost:8080/testRetryTemplate
結果:
2020-05-26 22:53:41.935 INFO 25208 --- [nio-8080-exec-4] o.j.r.r.c.RetryTemplateController : 開始執行業務 RetryTemplateImpl被調用,時間:22:53:41.936 RetryTemplateImpl被調用,時間:22:53:41.938 RetryTemplateImpl被調用,時間:22:53:44.939 RetryTemplateImpl被調用,時間:22:53:47.939 2020-05-26 22:53:50.940 INFO 25208 --- [pool-1-thread-1] o.j.r.r.service.RetryTemplate : 業務邏輯失敗,重試結束 重試失敗 恢復邏輯,記錄日誌等操做
完整的demo項目,請關注公衆號「前沿科技bot「併發送"重試機制"獲取。