有些場景需要我們對一些異常情況下面的任務進行重試恬叹,比如:調(diào)用遠程的RPC/RestTemplate或者Feign服務逼友,可能由于網(wǎng)絡抖動出現(xiàn)第一次調(diào)用失敗,嘗試幾次就可以恢復正常化借。當然調(diào)用內(nèi)部的其他服務也會遇到調(diào)用失敗的情況潜慎,這時候就需要通過一些方法來進行重試,比如通過while循環(huán)手動重復調(diào)用或是通過JDK/CGLib動態(tài)代理的方式來進行重試。但是這種方法比較笨重铐炫,且對原有邏輯代碼的侵入性比較大垒手。
Spring已經(jīng)為我們提供了封裝好的重試功能,spring-retry是spring提供的一個重試框架倒信,使我們可以通過@Retryable
和@Recover
注解來完成重試和重試失敗后的回調(diào)科贬。
一、Spring Retry配置
POM引入依賴:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
當不清楚引入依賴最新的版本和groupId的時候鳖悠,也可以在IDEA中通過它的提示快速添加:二榜掌、啟動類
在Spring Boot 應用入口啟動類,也就是配置類的上面加上@EnableRetry
注解乘综,表示讓重試機制生效憎账。
三、編寫Controller
簡單的Controller卡辰,其注入RestTemplate來調(diào)用其他服務接口 胞皱。代碼中被調(diào)用的http://www.guo.com:8080/v5/packageIndex/findByState/60
服務沒啟動,所以會拋出404異常,是為了觸發(fā)重試機制九妈。
@RestController
@Slf4j
@RequestMapping(value = "/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/restTemplate")
@Retryable(value = RestClientException.class, maxAttempts = 3,
backoff = @Backoff(delay = 5000L, multiplier = 2))
public JsonResult<String> findStateByStateFromPackageService() {
log.info("發(fā)起遠程API請求");
String response = null;
response = restTemplate.getForObject("http://www.guo.com:8080/v5/packageIndex/findByState/60", String.class);
log.info("Rest請求數(shù)據(jù):" + response);
return JsonResult.of(response, true, "成功調(diào)用");
}
}
- @Retryable注解的方法在發(fā)生異常時會重試反砌,參數(shù)說明:
value:當指定異常發(fā)生時會進行重試 ,HttpClientErrorException是RestClientException的子類。如果所有異常都進行重試萌朱,改成Exception.class
宴树。
include:和value一樣,默認空晶疼。如果 exclude也為空時酒贬,所有異常都重試
exclude:指定異常不重試,默認空冒晰。如果 include也為空時同衣,所有異常都重試
maxAttemps:最大重試次數(shù)竟块,默認3
backoff:重試等待策略壶运,默認空 - @Backoff注解為重試等待的策略,參數(shù)說明:
delay:指定重試的延時時間浪秘,默認為1000毫秒
multiplier:指定延遲的倍數(shù)蒋情,比如設置delay=5000,multiplier=2時耸携,第一次重試為5秒后棵癣,第二次為10(5x2)秒,第三次為20(10x2)秒夺衍。
四狈谊、啟動服務進行測試
啟動當前調(diào)用方服務后,向http://localhost:8085/rest/restTemplate發(fā)起請求,結果如下:
2020-10-04 12:23:39 [http-nio-8085-exec-1] INFO c.RestTemplateController:128 -發(fā)起遠程API請求
2020-10-04 12:23:48 [http-nio-8085-exec-1] INFO c.RestTemplateController:128 -發(fā)起遠程API請求
2020-10-04 12:24:02 [http-nio-8085-exec-1] INFO c.RestTemplateController:128 -發(fā)起遠程API請求
2020-10-04 12:24:06 [http-nio-8085-exec-1] ERROR o.a.c.c.C.[.[localhost].[/].[dispatcherServlet]:175 -Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/v5/packageIndex/findByState/60": Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException: Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect] with root cause
java.net.ConnectException: Connection refused: connect
at java.base/java.net.PlainSocketImpl.waitForConnect(Native Method)
從結果可以看出:
第一次請求失敗之后河劝,延遲后重試
第二次請求失敗之后壁榕,延遲后重試
第三次請求失敗之后,拋出異常
五赎瞎、@Recover注解
當重試次數(shù)達到設置的次數(shù)的時候牌里,還是失敗拋出異常,執(zhí)行@Recover
注解的回調(diào)函數(shù)务甥。
新建一個service提供重試和recover方法牡辽。
package com.pay.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import java.time.LocalTime;
/**
* @ClassName: PayService
* @Description: 模擬庫存扣減的service,實現(xiàn)扣減業(yè)務的可重試以及多長嘗試后失敗的回調(diào)處理敞临。
* @author: 郭秀志 jbcode@126.com
* @date: 2020/10/4 12:43
* @Copyright:
*/
@Service
@Slf4j
public class PayService {
private final int totalNum = 53;
@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
public int minGoodsnum(int num) throws Exception {
log.info("減庫存開始" + LocalTime.now());
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("illegal operation");
}
if (num <= 0) {
throw new IllegalArgumentException("數(shù)量不對");
}
log.info("減庫存執(zhí)行結束" + LocalTime.now());
return totalNum - num;
}
@Recover
public int recover(Exception e) {
log.warn("[recover method]減庫存失斕痢!S窗怼因妙!" + LocalTime.now());
//記日志到數(shù)據(jù)庫
//發(fā)送異常的郵件通知
return totalNum;
}
}
controller增加調(diào)用上面service的邏輯
@Autowired
private PayService payService;
@GetMapping("/retry")
public String getNum() throws Exception {
int i = payService.minGoodsnum(-1);
System.out.println("====" + i);
return "succeess";
}
測試recover,訪問http://localhost:8085/rest/retry票髓,控制臺打印信息:
2020-10-04 12:49:54 [http-nio-8085-exec-1] INFO PayService:27 -減庫存開始12:49:54.328000400
2020-10-04 12:49:54 [http-nio-8085-exec-1] ERROR PayService:31 -illegal operation
2020-10-04 12:49:56 [http-nio-8085-exec-1] INFO PayService:27 -減庫存開始12:49:56.337155100
2020-10-04 12:49:56 [http-nio-8085-exec-1] ERROR PayService:31 -illegal operation
2020-10-04 12:49:59 [http-nio-8085-exec-1] INFO PayService:27 -減庫存開始12:49:59.339616100
2020-10-04 12:49:59 [http-nio-8085-exec-1] ERROR PayService:31 -illegal operation
2020-10-04 12:49:59 [http-nio-8085-exec-1] WARN PayService:42 -[recover method]減庫存失斉屎!G⒐怠以故!12:49:59.341657600
====53