重試框架Guava-Retry和spring-Retry的使用示例
一 重試框架之Spring-Retry
Spring Retry 為 Spring 應(yīng)用程序提供了聲明性重試支持。 它用于Spring批處理、Spring集成、Apache Hadoop(等等)。它主要是針對可能拋出異常的一些調(diào)用操作,進(jìn)行有策略的重試
1. Spring-Retry的普通使用方式
準(zhǔn)備工作
我們只需要加上依賴:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>準(zhǔn)備一個任務(wù)方法,我這里是采用一個隨機(jī)整數(shù),根據(jù)不同的條件返回不同的值,或者拋出異常
package com.zgd.demo.thread.retry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;
/**
* @Author: zgd
* @Date: 18/11/21 17:01
* @Description:
*/
@Slf4j
public class RetryDemoTask {
/**
* 重試方法
* @return
*/
public static boolean retryTask(String param) {
log.info("收到請求參數(shù):{}",param);
int i = RandomUtils.nextInt(0,11);
log.info("隨機(jī)生成的數(shù):{}",i);
if (i == 0) {
log.info("為0,拋出參數(shù)異常.");
throw new IllegalArgumentException("參數(shù)異常");
}else if (i == 1){
log.info("為1,返回true.");
return true;
}else if (i == 2){
log.info("為2,返回false.");
return false;
}else{
//為其他
log.info("大于2,拋出自定義異常.");
throw new RemoteAccessException("大于2,拋出遠(yuǎn)程訪問異常");
}
}
}使用SpringRetryTemplate
這里可以寫我們的代碼了
package com.zgd.demo.thread.retry.spring;
import com.zgd.demo.thread.retry.RetryDemoTask;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: zgd
* @Date: 18/11/21 17:07
* @Description: spring-retry 重試框架
*/
@Slf4j
public class SpringRetryTemplateTest {
/**
* 重試間隔時間ms,默認(rèn)1000ms
* */
private long fixedPeriodTime = 1000L;
/**
* 最大重試次數(shù),默認(rèn)為3
*/
private int maxRetryTimes = 3;
/**
* 表示哪些異常需要重試,key表示異常的字節(jié)碼,value為true表示需要重試
*/
private Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
@Test
public void test() {
exceptionMap.put(RemoteAccessException.class,true);
// 構(gòu)建重試模板實(shí)例
RetryTemplate retryTemplate = new RetryTemplate();
// 設(shè)置重試回退操作策略,主要設(shè)置重試間隔時間
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(fixedPeriodTime);
// 設(shè)置重試策略,主要設(shè)置重試次數(shù)
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
Boolean execute = retryTemplate.execute(
//RetryCallback
retryContext -> {
boolean b = RetryDemoTask.retryTask("abc");
log.info("調(diào)用的結(jié)果:{}", b);
return b;
},
retryContext -> {
//RecoveryCallback
log.info("已達(dá)到最大重試次數(shù)或拋出了不重試的異常~~~");
return false;
}
);
log.info("執(zhí)行結(jié)果:{}",execute);
}
}簡單剖析下案例代碼,RetryTemplate 承擔(dān)了重試執(zhí)行者的角色,它可以設(shè)置SimpleRetryPolicy(重試策略,設(shè)置重試上限,重試的根源實(shí)體),F(xiàn)ixedBackOffPolicy(固定的回退策略,設(shè)置執(zhí)行重試回退的時間間隔)。
RetryTemplate通過execute提交執(zhí)行操作,需要準(zhǔn)備RetryCallback 和RecoveryCallback 兩個類實(shí)例,前者對應(yīng)的就是重試回調(diào)邏輯實(shí)例,包裝正常的功能操作,RecoveryCallback實(shí)現(xiàn)的是整個執(zhí)行操作結(jié)束的恢復(fù)操作實(shí)例.
只有在調(diào)用的時候拋出了異常,并且異常是在 exceptionMap 中配置的異常,才會執(zhí)行重試操作,否則就調(diào)用到 excute 方法的第二個執(zhí)行方法 RecoveryCallback 中
當(dāng)然,重試策略還有很多種,回退策略也是:
重試策略
- NeverRetryPolicy:只允許調(diào)用RetryCallback一次,不允許重試
- AlwaysRetryPolicy:允許無限重試,直到成功,此方式邏輯不當(dāng)會導(dǎo)致死循環(huán)
- SimpleRetryPolicy:固定次數(shù)重試策略,默認(rèn)重試最大次數(shù)為3次,RetryTemplate默認(rèn)使用的策略
- TimeoutRetryPolicy:超時時間重試策略,默認(rèn)超時時間為1秒,在指定的超時時間內(nèi)允許重試
- ExceptionClassifierRetryPolicy:設(shè)置不同異常的重試策略,類似組合重試策略,區(qū)別在于這里只區(qū)分不同異常的重試
- CircuitBreakerRetryPolicy:有熔斷功能的重試策略,需設(shè)置3個參數(shù)openTimeout、resetTimeout和delegate
- CompositeRetryPolicy:組合重試策略,有兩種組合方式,樂觀組合重試策略是指只要有一個策略允許即可以重試,悲觀組合重試策略是指只要有一個策略不允許即可以重試,但不管哪種組合方式,組合中的每一個策略都會執(zhí)行
重試回退策略
重試回退策略,指的是每次重試是立即重試還是等待一段時間后重試。默認(rèn)情況下是立即重試,如果需要配置等待一段時間后重試則需要指定回退策略BackoffRetryPolicy。
- NoBackOffPolicy:無退避算法策略,每次重試時立即重試
- FixedBackOffPolicy:固定時間的退避策略,需設(shè)置參數(shù)sleeper和backOffPeriod,sleeper指定等待策略,默認(rèn)是Thread.sleep,即線程休眠,backOffPeriod指定休眠時間,默認(rèn)1秒
- UniformRandomBackOffPolicy:隨機(jī)時間退避策略,需設(shè)置sleeper、minBackOffPeriod和maxBackOffPeriod,該策略在[minBackOffPeriod,maxBackOffPeriod之間取一個隨機(jī)休眠時間,minBackOffPeriod默認(rèn)500毫秒,maxBackOffPeriod默認(rèn)1500毫秒
- ExponentialBackOffPolicy:指數(shù)退避策略,需設(shè)置參數(shù)sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠時間,默認(rèn)100毫秒,maxInterval指定最大休眠時間,默認(rèn)30秒,multiplier指定乘數(shù),即下一次休眠時間為當(dāng)前休眠時間*multiplier
- ExponentialRandomBackOffPolicy:隨機(jī)指數(shù)退避策略,引入隨機(jī)乘數(shù)可以實(shí)現(xiàn)隨機(jī)乘數(shù)回退
我們可以根據(jù)自己的應(yīng)用場景和需求,使用不同的策略,不過一般使用默認(rèn)的就足夠了.
上面的代碼的話,我簡單的設(shè)置了重試間隔為1秒,重試的異常是 RemoteAccessException ,下面就是測試代碼的情況:重試第二次成功的情況:

重試一次以后,遇到了沒有指出需要重試的異常,直接結(jié)束重試,調(diào)用 retryContext

重試了三次后,達(dá)到了最大重試次數(shù),調(diào)用 retryContext

2. Spring-Retry的注解使用方式
既然是Spring家族的東西,那么自然就支持和Spring-Boot整合
準(zhǔn)備工作
依賴:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>代碼
在application啟動類上加上 @EnableRetry 的注解
@EnableRetry
public class Application {
...
}為了方便測試,我這里寫了一個SpringBootTest的測試基類,需要使用SpringBootTest的只要繼承這個類就好了
package com.zgd.demo.thread.test;
/**
* @Author: zgd
* @Date: 18/09/29 21:14
* @Description:
*/
import com.zgd.demo.thread.Application;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @Author: zgd
* @Date: 18/09/29 20:33
* @Description:
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Slf4j
public class MyBaseTest {
@Before
public void init() {
log.info("----------------測試開始---------------");
}
@After
public void after() {
log.info("----------------測試結(jié)束---------------");
}
}我們只要在需要重試的方法上加 @Retryable ,在重試失敗的回調(diào)方法上加 @Recover ,下面是這些注解的屬性

建一個service類
package com.zgd.demo.thread.retry.spring;
import com.zgd.demo.thread.retry.RetryDemoTask;
import com.zgd.demo.thread.test.MyBaseTest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.ExhaustedRetryException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
/**
* @Author: zgd
* @Date: 18/11/22 14:10
* @Description:
*/
@Service
@Slf4j
public class SpringRetryDemo {
/**
* 重試所調(diào)用方法
* @param param
* @return
*/
@Retryable(value = {RemoteAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 2000L,multiplier = 2))
public boolean call(String param){
return RetryDemoTask.retryTask(param);
}
/**
* 達(dá)到最大重試次數(shù),或拋出了一個沒有指定進(jìn)行重試的異常
* recover 機(jī)制
* @param e 異常
*/
@Recover
public boolean recover(Exception e,String param) {
log.error("達(dá)到最大重試次數(shù),或拋出了一個沒有指定進(jìn)行重試的異常:",e);
return false;
}
}然后我們調(diào)用這個service里面的call方法
package com.zgd.demo.thread.retry.spring;
import com.zgd.demo.thread.test.MyBaseTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @Author: zgd
* @Date: 18/11/22 14:23
* @Description:
*/
@Component
@Slf4j
public class SpringRetryDemoTest extends MyBaseTest {
@Autowired
private SpringRetryDemo springRetryDemo;
@Test
public void retry(){
boolean abc = springRetryDemo.call("abc");
log.info("--結(jié)果是:{}--",abc);
}
}這里我依然是 RemoteAccessException 的異常才重試, @Backoff(delay = 2000L,multiplier = 2)) 表示第一次間隔2秒,以后都是次數(shù)的2倍,也就是第二次4秒,第三次6秒.
來測試一下:遇到了沒有指定重試的異常,這里指定重試的異常是 @Retryable(value = {RemoteAccessException.class}... ,所以拋出參數(shù)異常 IllegalArgumentException 的時候,直接回調(diào) @Recover 的方法

重試達(dá)到最大重試次數(shù)時,調(diào)用 @Recover 的方法

重試到最后一次沒有報(bào)錯,返回false
![[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-C654zNXg-1653985491810)(https://i.imgur.com/fQshFGG.png)]](http://img.jbzj.com/file_images/article/202309/2023092609222213.png)
二 重試框架之Guava-Retry
Guava retryer工具與spring-retry類似,都是通過定義重試者角色來包裝正常邏輯重試,但是Guava retryer有更優(yōu)的策略定義,在支持重試次數(shù)和重試頻度控制基礎(chǔ)上,能夠兼容支持多個異常或者自定義實(shí)體對象的重試源定義,讓重試功能有更多的靈活性。Guava Retryer也是線程安全的,入口調(diào)用邏輯采用的是Java.util.concurrent.Callable的call方法,示例代碼如下:
pom.xml加入依賴
<!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying -->
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>更改一下測試的任務(wù)方法
package com.zgd.demo.thread.retry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;
/**
* @Author: zgd
* @Date: 18/11/21 17:01
* @Description:
*/
@Slf4j
public class RetryDemoTask {
/**
* 重試方法
* @return
*/
public static boolean retryTask(String param) {
log.info("收到請求參數(shù):{}",param);
int i = RandomUtils.nextInt(0,11);
log.info("隨機(jī)生成的數(shù):{}",i);
if (i < 2) {
log.info("為0,拋出參數(shù)異常.");
throw new IllegalArgumentException("參數(shù)異常");
}else if (i < 5){
log.info("為1,返回true.");
return true;
}else if (i < 7){
log.info("為2,返回false.");
return false;
}else{
//為其他
log.info("大于2,拋出自定義異常.");
throw new RemoteAccessException("大于2,拋出自定義異常");
}
}
}Guava
這里設(shè)定跟Spring-Retry不一樣,我們可以根據(jù)返回的結(jié)果來判斷是否重試,比如返回false我們就重試
package com.zgd.demo.thread.retry.guava;
import com.github.rholder.retry.*;
import com.zgd.demo.thread.retry.RetryDemoTask;
import org.junit.Test;
import org.springframework.remoting.RemoteAccessException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* @Author: zgd
* @Date: 18/11/23 10:32
* @Description:
*/
public class GuavaRetryTest {
@Test
public void fun01(){
// RetryerBuilder 構(gòu)建重試實(shí)例 retryer,可以設(shè)置重試源且可以支持多個重試源,可以配置重試次數(shù)或重試超時時間,以及可以配置等待時間間隔
Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
.retryIfExceptionOfType(RemoteAccessException.class)//設(shè)置異常重試源
.retryIfResult(res-> res==false) //設(shè)置根據(jù)結(jié)果重試
.withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)) //設(shè)置等待間隔時間
.withStopStrategy(StopStrategies.stopAfterAttempt(3)) //設(shè)置最大重試次數(shù)
.build();
try {
retryer.call(() -> RetryDemoTask.retryTask("abc"));
} catch (Exception e) {
e.printStackTrace();
}
}
}運(yùn)行測試一下
遇到了我們指定的需要重試的異常,進(jìn)行重試,間隔是3秒

重試次數(shù)超過了最大重試次數(shù)

返回為true,直接結(jié)束重試

遇到了沒有指定重試的異常,結(jié)束重試

返回false,重試

我們可以更靈活的配置重試策略,比如:
retryIfException
retryIfException,拋出 runtime 異常、checked 異常時都會重試,但是拋出 error 不會重試。
retryIfRuntimeException
retryIfRuntimeException 只會在拋 runtime 異常的時候才重試,checked 異常和error 都不重試。
retryIfExceptionOfType
retryIfExceptionOfType 允許我們只在發(fā)生特定異常的時候才重試,比如NullPointerException 和 IllegalStateException 都屬于 runtime 異常,也包括自定義的error。如:
retryIfExceptionOfType(NullPointerException.class)// 只在拋出空指針異常重試
retryIfResult
retryIfResult 可以指定你的 Callable 方法在返回值的時候進(jìn)行重試,如
// 返回false重試
.retryIfResult(Predicates.equalTo(false))
//以_error結(jié)尾才重試
.retryIfResult(Predicates.containsPattern("_error$"))
//返回為空時重試
.retryIfResult(res-> res==null)RetryListener
當(dāng)發(fā)生重試之后,假如我們需要做一些額外的處理動作,比如log一下異常,那么可以使用RetryListener。每次重試之后,guava-retrying 會自動回調(diào)我們注冊的監(jiān)聽??梢宰远鄠€RetryListener,會按照注冊順序依次調(diào)用。
.withRetryListener(new RetryListener {
@Override
public <T> void onRetry(Attempt<T> attempt) {
logger.error("第【{}】次調(diào)用失敗" , attempt.getAttemptNumber());
}
}
) 

總結(jié)
spring-retry 和 guava-retry 工具都是線程安全的重試,能夠支持并發(fā)業(yè)務(wù)場景的重試邏輯正確性。兩者都很好的將正常方法和重試方法進(jìn)行了解耦,可以設(shè)置超時時間,重試次數(shù),間隔時間,監(jiān)聽結(jié)果,都是不錯的框架但是明顯感覺得到,guava-retry在使用上更便捷,更靈活,能根據(jù)方法返回值來判斷是否重試,而Spring-retry只能根據(jù)拋出的異常來進(jìn)行重試
到此這篇關(guān)于重試框架Guava-Retry和spring-Retry的使用示例的文章就介紹到這了,更多相關(guān)Guava-Retry和spring-Retry重試框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot使用spring retry重試機(jī)制的操作詳解
- Java中使用Spring Retry實(shí)現(xiàn)重試機(jī)制的流程步驟
- spring @retryable不生效的一種場景分析
- Spring-Retry(重試機(jī)制)解讀
- SpringBoot中使用spring-retry 解決失敗重試調(diào)用
- Spring-retry實(shí)現(xiàn)循環(huán)重試功能
- spring-retry組件的使用教程
- Spring @Retryable注解輕松搞定循環(huán)重試功能
- Spring?Retry?實(shí)現(xiàn)樂觀鎖重試實(shí)踐記錄
相關(guān)文章
Java使用@Autowired注解獲取對象為null的幾種情況及解決方法
這篇文章主要給大家介紹了使用@Autowired注解獲取對象為null的幾種情況以及?解決方法,文中有詳細(xì)的代碼示例講解,具有一定的參考價(jià)值,需要的朋友可以參考下2023-09-09
Java多線程實(shí)現(xiàn)Callable接口
本文給大家分享的是使用Java多線程來實(shí)現(xiàn)callable接口的方法,以及使用方法,另外還有一個網(wǎng)友的實(shí)例,希望能夠?qū)Υ蠹艺莆認(rèn)ava多線程有所幫助。2016-06-06
java編程之AC自動機(jī)工作原理與實(shí)現(xiàn)代碼
這篇文章主要介紹了java編程之AC自動機(jī)的有關(guān)內(nèi)容,涉及其應(yīng)用場景,運(yùn)行原理,運(yùn)行過程,構(gòu)造方法及Java中的實(shí)現(xiàn)代碼,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
SpringBoot實(shí)現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項(xiàng)目實(shí)踐
本文主要介紹了SpringBoot實(shí)現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
java書店系統(tǒng)畢業(yè)設(shè)計(jì) 總體設(shè)計(jì)(1)
這篇文章主要介紹了java書店系統(tǒng)畢業(yè)設(shè)計(jì),第一步系統(tǒng)總體設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Java棧之鏈?zhǔn)綏4鎯Y(jié)構(gòu)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java棧之鏈?zhǔn)綏4鎯Y(jié)構(gòu)的實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04

