用兩行程式碼實現重試功能,spring-retry真是簡單而優雅

小白碼上飛發表於2022-03-18

背景

最近做的一個需求,需要呼叫第三方介面。正常情況下,介面的響應是符合要求的,只有在網路抖動等極少數的情況下,會存在超時情況。因為是小概率事件,所以一次超時之後,進行一次重試操作應該就可以了。重試很簡單,設定最多的重試次數,用一個迴圈來實現就好了。比如一次請求是這樣:

@Controller
public class RetryController {
    @Autowired
    private RetryRequestService retryRequestService;

    public String doSth(String param) {
        String result = retryRequestService.request(param);
        return "響應是" + result;
    }
}

改成重試三次,可以是這樣:

@Controller
public class RetryController {
    @Autowired
    private RetryRequestService retryRequestService;

    public String doSth(String param) {
        int count = 0;
        String result = "";
        while (count < 3) {
            try {
                result = retryRequestService.request(param);
                break;
            } catch (Exception e) {
                count++;
            }
        }
        return "響應是" + result;
    }
}

如果請求介面超時(拋異常)了,那麼會繼續進入下一次迴圈重試。如果在超時時間內獲取到了結果,那就結束迴圈,繼續往下走。

用倒是能用,但是太醜了。不好看,還狠狠的侵入了原有的程式碼。所以有沒有更優雅的方式呢?

快速接入spring-retry

這麼常用的東西,肯定有輪子啊!於是spring-retry閃亮登場!

這是一個屬於Spring全家桶的專案,也是被廣泛運用的元件。在這裡我預設你是個Spring Boot的專案了哈。

使用起來非常簡單,只需要三步。

1、引入依賴

<!--springboot專案都不用引入版本號-->
<dependency>
  <groupId>org.springframework.retry</groupId>
  <artifactId>spring-retry</artifactId>
</dependency>
<!--還是需要aop的支援的-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
</dependency>

2、在啟動類上加註解@EnableRetry

此舉是讓你的Spring Boot專案支援spring-retry的重試功能。像這樣:

@SpringBootApplication
@EnableRetry
@Slf4j
public class FastKindleApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(FastKindleApplication.class, args);
        String result = applicationContext.getBean(RetryController.class).doSth("");
        log.info(result);
    }
}

3、在需要重試的方法上加註解@Retryable

如上文所說,我們需要重試的方法是retryRequestService.request這個方法。那麼我們就在這個方法上加@Retryable註解。如下:

@Service
@Slf4j
public class RetryRequestService {
    @Autowired
    private OtherSystemSpi otherSystemSpi;

    @Retryable(value = RuntimeException.class, maxAttempts = 5, backoff = @Backoff(delay = 100))
    public String request(String param) {
        double random = Math.random();
        log.info("請求進來了,隨機值為:" + random);
        if (random > 0.1) {
            throw new RuntimeException("超時");
        }
        return otherSystemSpi.request(param);
    }
}

當然,我們這裡寫了個調皮的邏輯來模擬超時。如果隨機值大於0.1則丟擲一個RuntimeException異常。每次請求進來時都會輸出日誌。

我來解釋一下@Retryable註解中的資訊。

  • value = RuntimeException.class:是指方法丟擲RuntimeException異常時,進行重試。這裡可以指定你想要攔截的異常。
  • maxAttempts:是最大重試次數。如果不寫,則是預設3次。
  • backoff = @Backoff(delay = 100):是指重試間隔。delay=100意味著下一次的重試,要等100毫秒之後才能執行。

我們來執行一下,可以看到日誌輸出:

2022-03-15 23:51:19.754  INFO 3343 --- [main] c.e.fastkindle.FastKindleApplication     : Started FastKindleApplication in 0.347 seconds (JVM running for 0.536)
2022-03-15 23:51:19.762  INFO 3343 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.11030214774098712
2022-03-15 23:51:19.867  INFO 3343 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.09624689154608002
2022-03-15 23:51:19.867  INFO 3343 --- [main] c.e.fastkindle.FastKindleApplication     : 響應是mock

前兩次的隨機值都大於0.1,所以進行了重試,而且注意時間,都是間隔了大概100毫秒輸出的日誌。第三次的隨機值小於0.1,就直接返回資料了。

我又試了幾次,使五次請求的隨機值都大於0.1,則結果是進行了五次請求,最後丟擲了個異常。

2022-03-15 23:52:58.193  INFO 3449 --- [main] c.e.fastkindle.FastKindleApplication     : Started FastKindleApplication in 0.41 seconds (JVM running for 0.635)
2022-03-15 23:52:58.201  INFO 3449 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.5265644192525288
2022-03-15 23:52:58.303  INFO 3449 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.6343538744876432
2022-03-15 23:52:58.407  INFO 3449 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.5482463853575078
2022-03-15 23:52:58.511  INFO 3449 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.5624923285641071
2022-03-15 23:52:58.616  INFO 3449 --- [main] c.e.f.service.retry.RetryRequestService  : 請求進來了,隨機值為:0.305945622979098
Exception in thread "main" java.lang.RuntimeException: 超時
        at com.esparks.fastkindle.service.retry.RetryRequestService.request(RetryRequestService.java:24)
        at com.esparks.fastkindle.service.retry.RetryRequestService$$FastClassBySpringCGLIB$$50f0bdca.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

總結

好啦,我們今天就介紹一下快速的接入spring-retry來實現重試功能。更詳細的功能和實現原理,之後再詳細介紹吧(又給自己挖了個坑)

相關文章