重試框架Guava-Retry和spring-Retry的使用示例
一 重試框架之Spring-Retry
Spring Retry 為 Spring 應(yīng)用程序提供了聲明性重試支持。 它用于Spring批處理、Spring集成、Apache Hadoop(等等)。它主要是針對可能拋出異常的一些調(diào)用操作,進行有策略的重試
1. Spring-Retry的普通使用方式
準備工作
我們只需要加上依賴:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.2.2.RELEASE</version> </dependency>
準備一個任務(wù)方法,我這里是采用一個隨機整數(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("隨機生成的數(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,拋出遠程訪問異常"); } } }
使用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,默認1000ms * */ private long fixedPeriodTime = 1000L; /** * 最大重試次數(shù),默認為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)建重試模板實例 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("已達到最大重試次數(shù)或拋出了不重試的異常~~~"); return false; } ); log.info("執(zhí)行結(jié)果:{}",execute); } }
簡單剖析下案例代碼,RetryTemplate 承擔了重試執(zhí)行者的角色,它可以設(shè)置SimpleRetryPolicy(重試策略,設(shè)置重試上限,重試的根源實體),F(xiàn)ixedBackOffPolicy(固定的回退策略,設(shè)置執(zhí)行重試回退的時間間隔)。
RetryTemplate通過execute提交執(zhí)行操作,需要準備RetryCallback 和RecoveryCallback 兩個類實例,前者對應(yīng)的就是重試回調(diào)邏輯實例,包裝正常的功能操作,RecoveryCallback實現(xiàn)的是整個執(zhí)行操作結(jié)束的恢復(fù)操作實例.
只有在調(diào)用的時候拋出了異常,并且異常是在 exceptionMap
中配置的異常,才會執(zhí)行重試操作,否則就調(diào)用到 excute
方法的第二個執(zhí)行方法 RecoveryCallback
中
當然,重試策略還有很多種,回退策略也是:
重試策略
- NeverRetryPolicy:只允許調(diào)用RetryCallback一次,不允許重試
- AlwaysRetryPolicy:允許無限重試,直到成功,此方式邏輯不當會導(dǎo)致死循環(huán)
- SimpleRetryPolicy:固定次數(shù)重試策略,默認重試最大次數(shù)為3次,RetryTemplate默認使用的策略
- TimeoutRetryPolicy:超時時間重試策略,默認超時時間為1秒,在指定的超時時間內(nèi)允許重試
- ExceptionClassifierRetryPolicy:設(shè)置不同異常的重試策略,類似組合重試策略,區(qū)別在于這里只區(qū)分不同異常的重試
- CircuitBreakerRetryPolicy:有熔斷功能的重試策略,需設(shè)置3個參數(shù)openTimeout、resetTimeout和delegate
- CompositeRetryPolicy:組合重試策略,有兩種組合方式,樂觀組合重試策略是指只要有一個策略允許即可以重試,悲觀組合重試策略是指只要有一個策略不允許即可以重試,但不管哪種組合方式,組合中的每一個策略都會執(zhí)行
重試回退策略
重試回退策略,指的是每次重試是立即重試還是等待一段時間后重試。默認情況下是立即重試,如果需要配置等待一段時間后重試則需要指定回退策略BackoffRetryPolicy。
- NoBackOffPolicy:無退避算法策略,每次重試時立即重試
- FixedBackOffPolicy:固定時間的退避策略,需設(shè)置參數(shù)sleeper和backOffPeriod,sleeper指定等待策略,默認是Thread.sleep,即線程休眠,backOffPeriod指定休眠時間,默認1秒
- UniformRandomBackOffPolicy:隨機時間退避策略,需設(shè)置sleeper、minBackOffPeriod和maxBackOffPeriod,該策略在[minBackOffPeriod,maxBackOffPeriod之間取一個隨機休眠時間,minBackOffPeriod默認500毫秒,maxBackOffPeriod默認1500毫秒
- ExponentialBackOffPolicy:指數(shù)退避策略,需設(shè)置參數(shù)sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠時間,默認100毫秒,maxInterval指定最大休眠時間,默認30秒,multiplier指定乘數(shù),即下一次休眠時間為當前休眠時間*multiplier
- ExponentialRandomBackOffPolicy:隨機指數(shù)退避策略,引入隨機乘數(shù)可以實現(xiàn)隨機乘數(shù)回退
我們可以根據(jù)自己的應(yīng)用場景和需求,使用不同的策略,不過一般使用默認的就足夠了.
上面的代碼的話,我簡單的設(shè)置了重試間隔為1秒,重試的異常是 RemoteAccessException
,下面就是測試代碼的情況:重試第二次成功的情況:
重試一次以后,遇到了沒有指出需要重試的異常,直接結(jié)束重試,調(diào)用 retryContext
重試了三次后,達到了最大重試次數(shù),調(diào)用 retryContext
2. Spring-Retry的注解使用方式
既然是Spring家族的東西,那么自然就支持和Spring-Boot整合
準備工作
依賴:
<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); } /** * 達到最大重試次數(shù),或拋出了一個沒有指定進行重試的異常 * recover 機制 * @param e 異常 */ @Recover public boolean recover(Exception e,String param) { log.error("達到最大重試次數(shù),或拋出了一個沒有指定進行重試的異常:",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
的方法
重試達到最大重試次數(shù)時,調(diào)用 @Recover
的方法
重試到最后一次沒有報錯,返回false
二 重試框架之Guava-Retry
Guava retryer工具與spring-retry類似,都是通過定義重試者角色來包裝正常邏輯重試,但是Guava retryer有更優(yōu)的策略定義,在支持重試次數(shù)和重試頻度控制基礎(chǔ)上,能夠兼容支持多個異常或者自定義實體對象的重試源定義,讓重試功能有更多的靈活性。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("隨機生成的數(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)建重試實例 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(); } } }
運行測試一下
遇到了我們指定的需要重試的異常,進行重試,間隔是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 方法在返回值的時候進行重試,如
// 返回false重試 .retryIfResult(Predicates.equalTo(false)) //以_error結(jié)尾才重試 .retryIfResult(Predicates.containsPattern("_error$")) //返回為空時重試 .retryIfResult(res-> res==null)
RetryListener
當發(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ù)場景的重試邏輯正確性。兩者都很好的將正常方法和重試方法進行了解耦,可以設(shè)置超時時間,重試次數(shù),間隔時間,監(jiān)聽結(jié)果,都是不錯的框架但是明顯感覺得到,guava-retry在使用上更便捷,更靈活,能根據(jù)方法返回值來判斷是否重試,而Spring-retry只能根據(jù)拋出的異常來進行重試
到此這篇關(guān)于重試框架Guava-Retry和spring-Retry的使用示例的文章就介紹到這了,更多相關(guān)Guava-Retry和spring-Retry重試框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java使用@Autowired注解獲取對象為null的幾種情況及解決方法
這篇文章主要給大家介紹了使用@Autowired注解獲取對象為null的幾種情況以及?解決方法,文中有詳細的代碼示例講解,具有一定的參考價值,需要的朋友可以參考下2023-09-09SpringBoot實現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項目實踐
本文主要介紹了SpringBoot實現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-11-11java書店系統(tǒng)畢業(yè)設(shè)計 總體設(shè)計(1)
這篇文章主要介紹了java書店系統(tǒng)畢業(yè)設(shè)計,第一步系統(tǒng)總體設(shè)計,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10Java棧之鏈式棧存儲結(jié)構(gòu)的實現(xiàn)代碼
這篇文章主要介紹了Java棧之鏈式棧存儲結(jié)構(gòu)的實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04