SpringBoot系列(21):基於Guava

對於“接口/方法 重試”,相信很多小夥伴都聽說過,但是在實際項目中卻很少真正去實踐過,在本篇文章中,Debug將給各位小夥伴介紹一種“重試”機制的實現,即Guava_Retrying,相對於傳統的Spring_Retrying或者動態代理實現的重試功能而言,本文要介紹的Guava_Retrying機制使用起來將更加容易、靈活性更強!

老趙:“這個 接口/方法 調用又失敗了,老李啊,你去寫個重試功能吧!”。

老李:“他孃的,這接口調用咋又不行了。。。行吧,老子立馬給你擼一個重試功能” 。

這樣的對話,相信有些小夥伴會感覺似曾相識!特別是當自己在工位上安安靜靜的寫代碼時,會突然性的接到技術老大分配給自己的這種需求。。。沒啥好說的,只能潛下心,去研究研究了!

對於“重試”,那可是有場景限制的,不是什麼場景都適合重試,比如參數校驗不合法、寫操作等(因為要考慮到寫是否冪等)都不適合重試。

而諸如“遠程調用超時”、“網絡突然中斷”等業務場景則可以進行重試,在微服務 治理框架中,通常都有自己的重試與超時配置,比如Dubbo可以設置retries=1,timeout=500調用失敗只重試1次,超過500ms調用仍未返回則調用失敗(詳情可以觀看學習Debug錄製的“分佈式服務調度Dubbo實戰教程 http://www.fightjava.com/web/index/course/detail/2 ”)

對於“外部 RPC 調用”,或者“數據入庫”等操作,如果一次操作失敗,則可以進行多次重試,從而提高調用成功的可能性。


下面我們基於前面搭建的SpringBoot多模塊企業級項目,基於Guava_Retrying初步實現所謂的“重試功能”。工欲善其事必先利其器,首先當然是需要加入Guava_Retrying的依賴Jar,如下所示:



<dependency>

<groupid>com.github.rholder/<groupid>

<artifactid>guava-retrying/<artifactid>

<version>2.0.0/<version>

/<dependency>

之後,我們來寫個簡單的入門案例,先來 過一把“接口調用重試”的癮!

/**

* Guava_retrying機制實現重試

* @Author:debug (SteadyJack)

* @Link: weixin-> debug0868 qq-> 1948831260

* @Date: 2019/12/1 16:17

**/

public class RetryUtil {

private static final Logger log= LoggerFactory.getLogger(RetryUtil.class);



private static Integer i=1;




public static Integer execute() throws Exception{

log.info("----重試時 變量i的疊加邏輯----");

return i++;

}



public static void main(String[] args) {

//TODO:定義任務實例

Callable<string> callable= () -> {

Integer res=execute();

//當重試達到3 + 1次之後 我們就不玩了

if (res>3){

return res.toString();

}

return null;

};



//TODO:定義重試器

Retryer<string> retryer=RetryerBuilder.<string>newBuilder()

//TODO:當返回結果為Null時 - 執行重試

.retryIfResult(Predicates.isNull())

//TODO:當執行核心業務邏輯拋出RuntimeException - 執行重試


.retryIfRuntimeException()

//TODO:還可以自定義拋出何種異常時 - 執行重試

.retryIfExceptionOfType(IOException.class)

.build();

try {

retryer.call(callable);

} catch (ExecutionException | RetryException e) {

e.printStackTrace();

}

}
}/<string>/<string>/<string>

運行該main方法,可以得到如下的結果:

SpringBoot系列(21):基於Guava_Retrying機制實現重試功能

從該上述代碼中,我們得知“重試機制”功能實現的核心在於定義Retryer實例以及Callable任務運行實例 ,特別是Retryer實例,可以設置“什麼時機重試”。

除此之外,對於 Retryer實例 我們還可以設置“重試的次數”、“重試的時間間隔”、“每次重試時,定義Listener監聽一些操作邏輯”等等。如下代碼所示:

public static void main(String[] args) {

//TODO:定義任務實例

Callable<string> callable= () -> {

return null;

};



//TODO:每次重試時 監聽器執行的邏輯

RetryListener retryListener=new RetryListener() {

@Override

public void onRetry(Attempt attempt) {

Long curr=attempt.getAttemptNumber();

log.info("----每次重試時 監聽器執行的邏輯,當前已經是第 {} 次重試了----",curr);




if (curr == 3){

log.error("--重試次數已到,是不是得該執行一些補償邏輯,如發送短信、發送郵件...");

}

}

};



//TODO:定義重試器

Retryer<string> retryer=RetryerBuilder.<string>newBuilder()

//TODO:當返回結果為Null時 - 執行重試

.retryIfResult(Predicates.isNull())

//TODO:當執行核心業務邏輯拋出RuntimeException - 執行重試

.retryIfRuntimeException()

//TODO:還可以自定義拋出何種異常時 - 執行重試

.retryIfExceptionOfType(IOException.class)



//TODO:每次重試時的時間間隔為5s

.withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))

//TODO:重試次數為3次,3次之後就不重試了

.withStopStrategy(StopStrategies.stopAfterAttempt(3))


//TODO:每次重試時定義一個監聽器listener,監聽器的邏輯可以是 "日誌記錄"、"做一些補償操作"...

.withRetryListener(retryListener)

.build();



try {

retryer.call(callable);

} catch (ExecutionException | RetryException e) {

e.printStackTrace();

}

}/<string>/<string>
/<string>

其中,我們加入了“監聽器Listener”、“定義了重試次數”、“定義了每次重試的時間間隔”,這三個才是Guava_Retrying提供給開發者重量級的玩意,如下代碼所示!

//TODO:每次重試時的時間間隔為5s

.withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))

//TODO:重試次數為3次,3次之後就不重試了

.withStopStrategy(StopStrategies.stopAfterAttempt(3))

//TODO:每次重試時定義一個監聽器listener,監聽器的邏輯可以是 "日誌記錄"、"做一些補償操作"...

.withRetryListener(retryListener)

其中,“重試次數策略StopStrategies”、“重試的時間間隔設置策略WaitStrategies”中Guava_Retrying提供了許多種選擇,比如“重試次數可以是一個隨機數”、“重試的時間間隔也可以設置為某個區間範圍內的隨機數”等等,下圖為運行結果截圖:

SpringBoot系列(21):基於Guava_Retrying機制實現重試功能

下面,我們來擼一個真實的業務場景,即“調用某個接口的方法,用於獲取SysConfig配置表中某個字典配置記錄,如果該字典配置記錄不存在(即返回Null),那我們就重試3次,如果期間獲取到了,那麼就返回結果;3次過後,依舊為Null時,則執行一些補償性的措施:即發送郵件通知給到指定的人員,讓他們上去檢查檢查相應的數據狀況!”

下圖為 系統字典配置表SysConfig存儲的字典記錄,其中,沒有id=11的記錄,我們將拿著這個 id=11 來進行測試:

SpringBoot系列(21):基於Guava_Retrying機制實現重試功能

如下代碼為正常項目開發過程中我們自定義的Service及其方法:

/**

* Guava_Retrying重試機制的 小型真實案例

* @Author:debug (SteadyJack)

* @Link: weixin-> debug0868 qq-> 1948831260

* @Date: 2019/12/1 17:51

**/

@Service

public class RetryService {

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



@Autowired

private SysConfigMapper sysConfigMapper;



@Autowired

private EmailSendService emailSendService;



//TODO:獲取某個字典配置詳情

public SysConfig getConfigInfo(final Integer id){

SysConfig config=sysConfigMapper.selectByPrimaryKey(id);



if (config==null){

//TODO:當沒有查詢到該數據記錄時,執行重試邏輯

doRetry(id);

config=sysConfigMapper.selectByPrimaryKey(id);

}

return config;

}





//TODO:執行重試邏輯

private void doRetry(final Integer id){



//TODO:定義任務實例

Callable<sysconfig> callable= () -> {

return sysConfigMapper.selectByPrimaryKey(id);

};



//TODO:每次重試時 監聽器執行的邏輯

RetryListener retryListener=new RetryListener() {

@Override

public void onRetry(Attempt attempt) {

Long curr=attempt.getAttemptNumber();

log.info("----每次重試時 監聽器執行的邏輯,當前已經是第 {} 次重試了----",curr);




//當達到3次時 就執行一些補償性的措施,如發送郵件通知某些大佬….

if (curr == 3){

log.error("--重試次數已到,是不是得該執行一些補償邏輯,如發送短信、發送郵件...");



emailSendService.sendSimpleEmail("重試次數已到","請各位大佬上去檢查一下sysConfig是否存在","[email protected]");

}

}

};



//TODO:定義重試器

Retryer<sysconfig> retryer= RetryerBuilder.<sysconfig>newBuilder()

//TODO:當返回結果為 false 時 - 執行重試(即sysCofig為null)

.retryIfResult(Objects::isNull)

//TODO:當執行核心業務邏輯拋出RuntimeException - 執行重試

.retryIfRuntimeException()

//TODO:還可以自定義拋出何種異常時 - 執行重試

.retryIfExceptionOfType(IOException.class)



//TODO:每次重試時的時間間隔為10s (當然啦,實際項目中一般是不超過1s的,如500ms,這裡是為了方便模擬演示)

.withWaitStrategy(WaitStrategies.fixedWait(10L, TimeUnit.SECONDS))

//TODO:重試次數為3次,3次之後就不重試了

.withStopStrategy(StopStrategies.stopAfterAttempt(3))

//TODO:每次重試時定義一個監聽器listener,監聽器的邏輯可以是 "日誌記錄"、"做一些補償操作"...

.withRetryListener(retryListener)



.build();

try {

retryer.call(callable);

} catch (ExecutionException | RetryException e) {

e.printStackTrace();

}

}

}/<sysconfig>/<sysconfig>
/<sysconfig>

最後寫個Java Unit Test,即Java單元測試案例,如下所示:

@Autowired

private RetryService retryService;



@Test

public void method8() throws Exception{

final Integer id=11;



SysConfig entity=retryService.getConfigInfo(id);

log.info("---結果:{}",entity);

}

點擊運行該單元測試案例,啥事都不要做,等待運行結果,你會發現“重試”的效果我們已經實現了!如下所示:

SpringBoot系列(21):基於Guava_Retrying機制實現重試功能

我們再點擊運行該單元測試案例,然後在它運行了第1次重試機會之後,我們趕緊手動到數據庫將 id=12 的那條系統配置記錄,調整為 id=11 !然後再來看運行的結果,如下圖所示:

SpringBoot系列(21):基於Guava_Retrying機制實現重試功能

如下圖為“補償性措施”中的“發送郵件”:

SpringBoot系列(21):基於Guava_Retrying機制實現重試功能

好了,本篇文章我們就介紹到這裡了,建議各位小夥伴一定要照著文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”。其他相關的技術,感興趣的小夥伴可以關注Debug的技術公眾號,或者私信Debug!


分享到:


相關文章: