JAVA && Spring && SpringBoot2.x — 學習目錄
每篇一問
當我們調(diào)用一個接口時,可能因為網(wǎng)絡波動造成了系統(tǒng)之間超時,此時我們應該怎么辦?
重試唄~
是的,對于讀操作或者支持冪等的寫操作党晋,一般我們便可以進行重試操作。那如何進行重試?
不用擔心,Spring都處理好了...
正常邏輯和重試機制耦合度較高楔敌∑】妫基于這個些問題驻谆,spring-retry規(guī)范了了正常邏輯和重試邏輯卵凑,將正常邏輯和重試邏輯解耦。spring-retry是一個開源工具包胜臊,該工具把重試操作模板定制化勺卢,可以設置重試策略和回退策略。同時象对,重試執(zhí)行實例保證線程安全黑忱。spring-retry重試可以用Java代碼實現(xiàn)也可以用注解@Retryable方式實現(xiàn),這里spring-retry提倡以注解的方式對方法進行重試勒魔。
1甫煞、如何使用Spring-Retry進行重試
1. pom文件需要引入的依賴
<!-- SpringRetry重試機制 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2. 啟動類上配置自動加載
在SpringBoot 引入新技術(shù),一般需要2步:
(1)pom 文件加入依賴冠绢;
(2)啟動類上加上@EnableXXX注解抚吠;
@SpringBootApplication
@EnableRetry //開啟retry重試機制
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
3. @Retryable和@Recover的使用
@Service
public class RetryService {
//開啟日志控制
private final static Logger logger = LoggerFactory.getLogger(RetryService.class);
private final int totalNum = 100;
/**
* value:出現(xiàn)Exception.class進行重試,指定特定的異常
* ? include:和value一樣弟胀,默認為空楷力,當exclude也為空時,默認所以異常
* ? exclude:指定不處理的異常
* ?maxAttempts:最大重試次數(shù)孵户,默認3次
* ?backoff:重試等待策略萧朝,默認使用@Backoff,@Backoff的value默認為1000L夏哭,
* 我們設置為2000L检柬;multiplier(指定延遲倍數(shù))默認為0,表示固定暫停1秒后進行重試方庭,
* 如果把multiplier設置為1.5厕吉,則第一次重試為2秒,第二次為3秒械念,第三次為4.5
*
* @param num
* @return
*/
@Retryable(value = Exception.class, maxAttempts = 3,
backoff = @Backoff(delay = 2000L, multiplier = 1.5))
public int retry(int num) {
logger.info("{}減庫存開始", LocalTime.now());
try {
int i = 1 / 0;
} catch (Exception e) {
logger.error("illegal");
}
if (num <= 0) {
throw new IllegalArgumentException("數(shù)量不對");
}
logger.info("減庫存執(zhí)行結(jié)束" + LocalTime.now());
return totalNum - num;
}
/**
* 作為降級方法調(diào)用的注釋(重試調(diào)用完依舊失敗头朱,觸發(fā)降級)。
* 參數(shù)(可選):合適的Throwable類型的第一個參數(shù)(或Throwable的子類型)龄减,若是參數(shù)為空项钮,只有其他降級注釋標注的方法均不復合時,才會執(zhí)行改方法希停。(后續(xù)可以攜帶請求參數(shù)烁巫,但是Throwable必須為第一個參數(shù))
* 此時該請求參數(shù)明確
* 返回值:@Retryable 方法類型相同的返回值。
*/
@Recover
public int recover(IllegalArgumentException e,int num) {
log.warn("recover減庫存失敵枘堋Q窍丁!违崇!" + LocalTime.now());
return totalNum;
}
/**
* 作為降級方法調(diào)用的注釋(重試調(diào)用完依舊失敗阿弃,觸發(fā)降級)诊霹。
* 參數(shù)(可選):合適的Throwable類型的第一個參數(shù)(或Throwable的子類型),若是參數(shù)為空渣淳,只有其他降級注釋標注的方法均不復合時脾还,才會執(zhí)行改方法。
* 返回值:@Retryable 方法類型相同的返回值入愧。
*/
@Recover
public int recover2() {
log.warn("recover2減庫存失敗22221陕!棺蛛!" + LocalTime.now());
return totalNum;
}
4. 測試方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class RetryServiceTest {
@Autowired
RetryService retryService;
@Test
public void testRetry() {
int i = retryService.retry(-1);
System.out.println("數(shù)據(jù)是: "+ i );
}
}
5. 測試效果
@Retryable注解
被注解的方法發(fā)生異常時會重試
value:指定發(fā)生的異常進行重試
include:和value一樣怔蚌,默認空,當exclude也為空時旁赊,所有異常都重試
exclude:指定異常不重試媚创,默認空,當include也為空時彤恶,所有異常都重試
maxAttemps:重試次數(shù)钞钙,默認3
backoff:重試補償機制,默認沒有声离。
exceptionExpression:spEL表達式芒炼,若配置include或exclude參數(shù)那么該配置不會生效,其作用等效于include或exclude术徊。格式#{@bean.methodName(#root)}
本刽。methodName
的返回值為boolean類型。#root
是異常類赠涮,即用戶可以在代碼中判斷是否進行重試子寓。
@Retryable(maxAttempts = 2,
backoff = @Backoff(delay = 2000L, multiplier = 1.5),
exceptionExpression = "#{@retryService.xxx(#root)}")
public int retry(int num) {
log.info("{}減庫存開始", LocalTime.now());
if (num <= 0) {
throw new RuntimeException("數(shù)量不對");
}
log.info("減庫存執(zhí)行結(jié)束" + LocalTime.now());
return totalNum - num;
}
public Boolean xxx(Exception e) {
//根據(jù)異常信息決定是否重試!K癯斜友!
log.info("判斷是否異常");
return true;
}
@Backoff注解
delay:指定延遲后重試
multiplier:指定延遲的倍數(shù),比如delay=5000l,multiplier=2時垃它,第一次重試為5秒后鲜屏,第二次為10秒,第三次為20秒
@Recover
當重試到達指定次數(shù)時国拇,被注解的方法將被回調(diào)洛史,可以在該方法中進行日志處理。需要注意的是發(fā)生的異常和入?yún)㈩愋鸵恢聲r才會回調(diào)酱吝。
下面咱們講一下注意事項:
- 對于非冪等的讀操作也殖,應該禁止使用重試機制;
- 使用了@Retryable的方法不能在本類被調(diào)用(不能被套嵌使用)务热,不然重試機制不會生效忆嗜。由于retry用到了aspect增強浪漠,所有會有aspect的坑,就是方法內(nèi)部調(diào)用霎褐,會使aspect增強失效,那么retry當然也會失效该镣。
- 注解方法的異常不能通過try-catch捕獲冻璃,應該throw出去,被捕獲后處理损合,本質(zhì)上就是AOP思想的使用省艳。