springboot系列——重試機制原理和應用,還有比這個講的更好的嗎(附完整源碼)

若是有,請轉給我!服務器

1. 理解重試機制網絡

「重試是爲了提升成功的可能性「架構

反過來理解,任何可能失敗且容許重試操做的場景,就適合使用重試機制。但有了重試機制就必定能成功嗎?顯然不是。若是不成功就一直重試,這種處理方式會使得業務線程一直被重試佔用,這樣會致使服務的負載線程暴增直至服務宕機,所以須要限制重試次數。失敗狀況下,咱們須要作後續的操做,若是是數據庫操做的重試,須要回滾事物;若是是服務調用的重試,須要郵件報警通知運維開發人員,恢復服務。併發

對於服務接口調用,多是由於網絡波動致使超時失敗,這時候全部重試次數是在很短期內發起的話,就很容易所有超時失敗,所以超時機制還須要引入重試動做之間時間間隔以及第一次失敗後延遲多長時間再開始重試等機制。

重試機制要素

  • 限制重試次數
  • 每次重試的時間間隔
  • 最終失敗結果的報警或事物回滾
  • 在特定失敗異常事件狀況下選擇重試

2. 總結重試機制使用場景

任何可能失敗且容許重試操做的場景,就適合使用重試機制。那麼在分佈式系統開發環境中,哪些場景須要是使用重試機制呢。

  • 樂觀鎖機制保證數據安全的數據更新場景,如帳戶信息的金額數據更新。
  • 微服務的分佈式架構下,服務的調用因超時而失敗。

3. spring-retry重試組件

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;
}

 

配置元數據狀況:

  • 重試次數爲3
  • 第一次重試延遲2s
  • 每次重試時間間隔是前一次1.5倍
  • Exception類異常狀況下重試

測試:

啓動應用,瀏覽器輸入: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
回調方法執行!!!!

 

4. 手寫一個基於註解的重試組件

註解類:

/**
 * 重試註解
 */
@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  : 重試失敗,未進行任何補全,此爲默認補全:打出錯誤日誌

 

5. 重試機制下會出現的問題

冪等性問題:

在分佈式架構下,服務之間調用會由於網絡緣由出現超時失敗狀況,而重試機制會重複屢次調用服務,可是對於被調用放,就可能收到了屢次調用。若是被調用方不具備天生的冪等性,那就須要增長服務調用的判重模塊,並對每次調用都添加一個惟一的id。

大量請求超時堆積:

超高併發下,大量的請求若是都進行超時重試的話,若是你的重試時間設置不安全的話,會致使大量的請求佔用服務器線程進行重試,這時候服務器線程負載就會暴增,致使服務器宕機。對於這種超高併發下的重試設計,咱們不能讓重試放在業務線程,而是統一由異步任務來執行。

6. 模板方法設計模式實現異步重試機制

模板方法設計模式來實現異步重試機制

全部業務類繼承重試模板類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;
        }
    }
    
}

 

  • 業務實現類在doBiz方法內實現業務過程
  • 全部業務實現一個恢復類,實現Recover接口,重試屢次失敗後執行恢復邏輯

測試:

啓動應用,瀏覽器輸入: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「併發送"重試機制"獲取。

相關文章
相關標籤/搜索