springboot重試策略

語言: CN / TW / HK

前言

在我們日常開發中,會遇到比如A服務調用B服務RPC超時、獲取鎖失敗等場景,我們需要進行重試操作,為了不侵入代碼優雅的進行重試,我們可以利用Spring提供的spring-retry組件來實現。

開始

  1. 引入依賴

核心依賴spring-retry,另外因為spring的retry底層是以AOP實現的,我們也需要引入aspectj

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.5</version>
        </dependency>
  1. 開啟@EnableRetry支持

@EnableRetry支持方法和類、接口、枚舉級別的重試

@SpringBootApplication
@EnableRetry
public class SpringbootRetryApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootRetryApplication.class, args);
    }
}
  1. 編寫重試service服務

@Retryable參數説明:

  • value:指定異常進行重試
  • maxAttempts:重試次數,默認3次
  • backoff:補償策略
  • include:和value一樣,默認空,當exclude也為空時,所有異常都重試
  • exclude:指定異常不重試,默認空,當include也為空時,所有異常都重試
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteTimeoutException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

/**
 * @author lb
 */
@Service
public class RetryService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RetryService.class);

    @Retryable(
            // 指定發生的異常進行重試
            value = {RemoteAccessException.class, RemoteTimeoutException.class} ,
            // 重試次數
            maxAttempts = 3,
            // 補償策略 delay:延遲多久執行補償機制,multiplier:指定延遲的倍數,比如delay=5000,multiplier=2時,第一次重試為5秒後,第二次為10秒,第三次為20秒
            backoff = @Backoff(delay = 5000L,multiplier = 2)
    )
    public String call(){
        LOGGER.info("執行重試方法.....");
//        throw new RemoteAccessException("RPC訪問異常");
        throw new RemoteTimeoutException("RPC調用超時異常");
    }

    @Recover
    public String recover(RemoteAccessException e){
        LOGGER.info("最終重試失敗,執行RemoteAccess補償機制 error : {}",e.getMessage());
        return "ok";
    }

    @Recover
    public String recover(RemoteTimeoutException e){
        LOGGER.info("最終重試失敗,執行RemoteTimeout補償機制 error : {}",e.getMessage());
        return "ok";
    }

}

有幾點需要注意的地方:

  • 重試機制的service服務必須單獨創建一個class,不能寫在接口的實現類裏,否則會拋出ex
  • @Retryable是以AOP實現的,所以如果@Retryable標記的方法被其他方法調用了,則不會進行重試。
  • recover方法的返回值類型必須和call()方法的返回值類型一致

測試

  • 編寫測試類
import com.lb.springboot.retry.service.RetryService;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootRetryApplicationTests {

    @Autowired
    private RetryService retryService;

    @Test
    public void testRetry(){
        String result = retryService.call();
        MatcherAssert.assertThat(result, Matchers.is("ok"));
    }

}
  • 執行結果:
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-12-17 14:15:58.083  INFO 71362 --- [           main] c.l.s.r.SpringbootRetryApplicationTests  : Starting SpringbootRetryApplicationTests on yunnashengdeMacBook-Pro.local with PID 71362 (started by yunnasheng in /Users/yunnasheng/work/github-workspace/springboot-retry)
2020-12-17 14:15:58.086  INFO 71362 --- [           main] c.l.s.r.SpringbootRetryApplicationTests  : No active profile set, falling back to default profiles: default
2020-12-17 14:15:59.238  INFO 71362 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-12-17 14:15:59.470  INFO 71362 --- [           main] c.l.s.r.SpringbootRetryApplicationTests  : Started SpringbootRetryApplicationTests in 1.686 seconds (JVM running for 2.582)

2020-12-17 14:15:59.668  INFO 71362 --- [           main] c.l.s.retry.service.RetryService         : 執行重試方法.....
2020-12-17 14:16:04.674  INFO 71362 --- [           main] c.l.s.retry.service.RetryService         : 執行重試方法.....
2020-12-17 14:16:14.675  INFO 71362 --- [           main] c.l.s.retry.service.RetryService         : 執行重試方法.....
2020-12-17 14:16:14.676  INFO 71362 --- [           main] c.l.s.retry.service.RetryService         : 最終重試失敗,執行RemoteTimeout補償機制 error : RPC調用超時異常

案例源碼:https://github.com/yunnasheng/springboot-retry.git