日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java retry(重试) spring retry, guava retrying 详解

發布時間:2024/2/28 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java retry(重试) spring retry, guava retrying 详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載 自?http://blog.51cto.com/9250070/2156431

系列說明

java retry 的一步步實現機制。

java-retry 源碼地址

情景導入

簡單的需求

產品經理:實現一個按條件,查詢用戶信息的服務。

小明:好的。沒問題。

代碼

  • UserService.java
public interface UserService {/*** 根據條件查詢用戶信息* @param condition 條件* @return User 信息*/User queryUser(QueryUserCondition condition);}
  • UserServiceImpl.java
public class UserServiceImpl implements UserService {private OutService outService;public UserServiceImpl(OutService outService) {this.outService = outService;}@Overridepublic User queryUser(QueryUserCondition condition) {outService.remoteCall();return new User();}}

談話

項目經理:這個服務有時候會失敗,你看下。

小明:OutService?在是一個 RPC 的外部服務,但是有時候不穩定。

項目經理:如果調用失敗了,你可以調用的時候重試幾次。你去看下重試相關的東西

重試

重試作用

對于重試是有場景限制的,不是什么場景都適合重試,比如參數校驗不合法、寫操作等(要考慮寫是否冪等)都不適合重試。

遠程調用超時、網絡突然中斷可以重試。在微服務治理框架中,通常都有自己的重試與超時配置,比如dubbo可以設置retries=1,timeout=500調用失敗只重試1次,超過500ms調用仍未返回則調用失敗。

比如外部 RPC 調用,或者數據入庫等操作,如果一次操作失敗,可以進行多次重試,提高調用成功的可能性

V1.0 支持重試版本

思考

小明:我手頭還有其他任務,這個也挺簡單的。5 分鐘時間搞定他。

實現

  • UserServiceRetryImpl.java
public class UserServiceRetryImpl implements UserService {@Overridepublic User queryUser(QueryUserCondition condition) {int times = 0;OutService outService = new AlwaysFailOutServiceImpl();while (times < RetryConstant.MAX_TIMES) {try {outService.remoteCall();return new User();} catch (Exception e) {times++;if(times >= RetryConstant.MAX_TIMES) {throw new RuntimeException(e);}}}return null;}}

V1.1 代理模式版本

易于維護

項目經理:你的代碼我看了,功能雖然實現了,但是盡量寫的易于維護一點。

小明:好的。(心想,是說要寫點注釋什么的?)

代理模式

為其他對象提供一種代理以控制對這個對象的訪問。

在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介作用。

其特征是代理與委托類有同樣的接口。

實現

小明想到以前看過的代理模式,心想用這種方式,原來的代碼改動量較少,以后想改起來也方便些。

  • UserServiceProxyImpl.java
public class UserServiceProxyImpl implements UserService {private UserService userService = new UserServiceImpl();@Overridepublic User queryUser(QueryUserCondition condition) {int times = 0;while (times < RetryConstant.MAX_TIMES) {try {return userService.queryUser(condition);} catch (Exception e) {times++;if(times >= RetryConstant.MAX_TIMES) {throw new RuntimeException(e);}}}return null;}}

V1.2 動態代理模式

方便拓展

項目經理:小明啊,這里還有個方法也是同樣的問題。你也給加上重試吧。

小明:好的。

小明心想,我在寫一個代理,但是轉念冷靜了下來,如果還有個服務也要重試怎么辦呢?

  • RoleService.java
public interface RoleService {/*** 查詢* @param user 用戶信息* @return 是否擁有權限*/boolean hasPrivilege(User user);}

代碼實現

  • DynamicProxy.java
public class DynamicProxy implements InvocationHandler {private final Object subject;public DynamicProxy(Object subject) {this.subject = subject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {int times = 0;while (times < RetryConstant.MAX_TIMES) {try {// 當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用return method.invoke(subject, args);} catch (Exception e) {times++;if (times >= RetryConstant.MAX_TIMES) {throw new RuntimeException(e);}}}return null;}/*** 獲取動態代理** @param realSubject 代理對象*/public static Object getProxy(Object realSubject) {// 我們要代理哪個真實對象,就將該對象傳進去,最后是通過該真實對象來調用其方法的InvocationHandler handler = new DynamicProxy(realSubject);return Proxy.newProxyInstance(handler.getClass().getClassLoader(),realSubject.getClass().getInterfaces(), handler);}}
  • 測試代碼
@Test public void failUserServiceTest() {UserService realService = new UserServiceImpl();UserService proxyService = (UserService) DynamicProxy.getProxy(realService);User user = proxyService.queryUser(new QueryUserCondition());LOGGER.info("failUserServiceTest: " + user); }@Test public void roleServiceTest() {RoleService realService = new RoleServiceImpl();RoleService proxyService = (RoleService) DynamicProxy.getProxy(realService);boolean hasPrivilege = proxyService.hasPrivilege(new User());LOGGER.info("roleServiceTest: " + hasPrivilege); }

V1.3 動態代理模式增強

對話

項目經理:小明,你動態代理的方式是挺會偷懶的,可是我們有的類沒有接口。這個問題你要解決一下。

小明:好的。(誰?寫服務竟然不定義接口)

  • ResourceServiceImpl.java
public class ResourceServiceImpl {/*** 校驗資源信息* @param user 入參* @return 是否校驗通過*/public boolean checkResource(User user) {OutService outService = new AlwaysFailOutServiceImpl();outService.remoteCall();return true;}}

字節碼技術

小明看了下網上的資料,解決的辦法還是有的。

  • CGLIB

CGLIB?是一個功能強大、高性能和高質量的代碼生成庫,用于擴展JAVA類并在運行時實現接口。

  • javassist

javassist?(Java編程助手)使Java字節碼操作變得簡單。

它是Java中編輯字節碼的類庫;它允許Java程序在運行時定義新類,并在JVM加載類文件時修改類文件。

與其他類似的字節碼編輯器不同,Javassist提供了兩個級別的API:源級和字節碼級。

如果用戶使用源代碼級API,他們可以編輯類文件,而不需要了解Java字節碼的規范。

整個API只使用Java語言的詞匯表進行設計。您甚至可以以源文本的形式指定插入的字節碼;Javassist動態編譯它。

另一方面,字節碼級API允許用戶直接編輯類文件作為其他編輯器。

  • ASM

ASM?是一個通用的Java字節碼操作和分析框架。

它可以用來修改現有的類或動態地生成類,直接以二進制形式。

ASM提供了一些通用的字節碼轉換和分析算法,可以從這些算法中構建自定義復雜的轉換和代碼分析工具。

ASM提供與其他Java字節碼框架類似的功能,但主要關注性能。

因為它的設計和實現都盡可能地小和快,所以非常適合在動態系統中使用(當然也可以以靜態的方式使用,例如在編譯器中)。

實現

小明看了下,就選擇使用 CGLIB。

  • CglibProxy.java
public class CglibProxy implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {int times = 0;while (times < RetryConstant.MAX_TIMES) {try {//通過代理子類調用父類的方法return methodProxy.invokeSuper(o, objects);} catch (Exception e) {times++;if (times >= RetryConstant.MAX_TIMES) {throw new RuntimeException(e);}}}return null;}/*** 獲取代理類* @param clazz 類信息* @return 代理類結果*/public Object getProxy(Class clazz){Enhancer enhancer = new Enhancer();//目標對象類enhancer.setSuperclass(clazz);enhancer.setCallback(this);//通過字節碼技術創建目標對象類的子類實例作為代理return enhancer.create();}}
  • 測試
@Test public void failUserServiceTest() {UserService proxyService = (UserService) new CglibProxy().getProxy(UserServiceImpl.class);User user = proxyService.queryUser(new QueryUserCondition());LOGGER.info("failUserServiceTest: " + user); }@Test public void resourceServiceTest() {ResourceServiceImpl proxyService = (ResourceServiceImpl) new CglibProxy().getProxy(ResourceServiceImpl.class);boolean result = proxyService.checkResource(new User());LOGGER.info("resourceServiceTest: " + result); }

V2.0 AOP 實現

對話

項目經理:小明啊,最近我在想一個問題。不同的服務,重試的時候次數應該是不同的。因為服務對穩定性的要求各不相同啊。

小明:好的。(心想,重試都搞了一周了,今天都周五了。)

下班之前,小明一直在想這個問題。剛好周末,花點時間寫個重試小工具吧。

設計思路

  • 技術支持

spring

java 注解

  • 注解定義

注解可在方法上使用,定義需要重試的次數

  • 注解解析

攔截指定需要重試的方法,解析對應的重試次數,然后進行對應次數的重試。

實現

  • Retryable.java
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Retryable {/*** Exception type that are retryable.* @return exception type to retry*/Class<? extends Throwable> value() default RuntimeException.class;/*** 包含第一次失敗* @return the maximum number of attempts (including the first failure), defaults to 3*/int maxAttempts() default 3;}
  • RetryAspect.java
@Aspect @Component public class RetryAspect {@Pointcut("execution(public * com.github.houbb.retry.aop..*.*(..)) &&" +"@annotation(com.github.houbb.retry.aop.annotation.Retryable)")public void myPointcut() {}@Around("myPointcut()")public Object around(ProceedingJoinPoint point) throws Throwable {Method method = getCurrentMethod(point);Retryable retryable = method.getAnnotation(Retryable.class);//1. 最大次數判斷int maxAttempts = retryable.maxAttempts();if (maxAttempts <= 1) {return point.proceed();}//2. 異常處理int times = 0;final Class<? extends Throwable> exceptionClass = retryable.value();while (times < maxAttempts) {try {return point.proceed();} catch (Throwable e) {times++;// 超過最大重試次數 or 不屬于當前處理異常if (times >= maxAttempts ||!e.getClass().isAssignableFrom(exceptionClass)) {throw new Throwable(e);}}}return null;}private Method getCurrentMethod(ProceedingJoinPoint point) {try {Signature sig = point.getSignature();MethodSignature msig = (MethodSignature) sig;Object target = point.getTarget();return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}

方法的使用

  • fiveTimes()

當前方法一共重試 5 次。
重試條件:服務拋出?AopRuntimeExption

@Override @Retryable(maxAttempts = 5, value = AopRuntimeExption.class) public void fiveTimes() {LOGGER.info("fiveTimes called!");throw new AopRuntimeExption(); }
  • 測試日志
2018-08-08 15:49:33.814 INFO [main] com.github.houbb.retry.aop.service.impl.UserServiceImpl:66 - fiveTimes called! 2018-08-08 15:49:33.815 INFO [main] com.github.houbb.retry.aop.service.impl.UserServiceImpl:66 - fiveTimes called! 2018-08-08 15:49:33.815 INFO [main] com.github.houbb.retry.aop.service.impl.UserServiceImpl:66 - fiveTimes called! 2018-08-08 15:49:33.815 INFO [main] com.github.houbb.retry.aop.service.impl.UserServiceImpl:66 - fiveTimes called! 2018-08-08 15:49:33.815 INFO [main] com.github.houbb.retry.aop.service.impl.UserServiceImpl:66 - fiveTimes called!java.lang.reflect.UndeclaredThrowableException ...

V3.0 spring-retry 版本

對話

周一來到公司,項目經理又和小明談了起來。

項目經理:重試次數是滿足了,但是重試其實應該講究策略。比如調用外部,第一次失敗,可以等待 5S 在次調用,如果又失敗了,可以等待 10S 再調用。。。

小明:了解。

思考

可是今天周一,還有其他很多事情要做。

小明在想,沒時間寫這個呀??纯淳W上有沒有現成的。

spring-retry

Spring Retry?為 Spring 應用程序提供了聲明性重試支持。 它用于Spring批處理、Spring集成、Apache Hadoop(等等)的Spring。

在分布式系統中,為了保證數據分布式事務的強一致性,大家在調用RPC接口或者發送MQ時,針對可能會出現網絡抖動請求超時情況采取一下重試操作。 大家用的最多的重試方式就是MQ了,但是如果你的項目中沒有引入MQ,那就不方便了。

還有一種方式,是開發者自己編寫重試機制,但是大多不夠優雅。

注解式使用

  • RemoteService.java

重試條件:遇到?RuntimeException

重試次數:3

重試策略:重試的時候等待 5S, 后面時間依次變為原來的 2 倍數。

熔斷機制:全部重試失敗,則調用?recover()?方法。

@Service public class RemoteService {private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);/*** 調用方法*/@Retryable(value = RuntimeException.class,maxAttempts = 3,backoff = @Backoff(delay = 5000L, multiplier = 2))public void call() {LOGGER.info("Call something...");throw new RuntimeException("RPC調用異常");}/*** recover 機制* @param e 異常*/@Recoverpublic void recover(RuntimeException e) {LOGGER.info("Start do recover things....");LOGGER.warn("We meet ex: ", e);}}
  • 測試
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) public class RemoteServiceTest {@Autowiredprivate RemoteService remoteService;@Testpublic void test() {remoteService.call();}}
  • 日志
2018-08-08 16:03:26.409 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Call something... 2018-08-08 16:03:31.414 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Call something... 2018-08-08 16:03:41.416 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Call something... 2018-08-08 16:03:41.418 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Start do recover things.... 2018-08-08 16:03:41.425 WARN 1433 --- [ main] c.g.h.r.spring.service.RemoteService : We meet ex: java.lang.RuntimeException: RPC調用異常at com.github.houbb.retry.spring.service.RemoteService.call(RemoteService.java:38) ~[classes/:na] ...

三次調用的時間點:

2018-08-08 16:03:26.409 2018-08-08 16:03:31.414 2018-08-08 16:03:41.416

缺陷

spring-retry 工具雖能優雅實現重試,但是存在兩個不友好設計:

一個是重試實體限定為?Throwable?子類,說明重試針對的是可捕捉的功能異常為設計前提的,但是我們希望依賴某個數據對象實體作為重試實體,
但 sping-retry框架必須強制轉換為Throwable子類。

另一個就是重試根源的斷言對象使用的是 doWithRetry 的 Exception 異常實例,不符合正常內部斷言的返回設計。

Spring Retry 提倡以注解的方式對方法進行重試,重試邏輯是同步執行的,重試的“失敗”針對的是Throwable,
如果你要以返回值的某個狀態來判定是否需要重試,可能只能通過自己判斷返回值然后顯式拋出異常了。

@Recover?注解在使用時無法指定方法,如果一個類中多個重試方法,就會很麻煩。

注解介紹

@EnableRetry

表示是否開始重試。

序號屬性類型默認值說明
1proxyTargetClassbooleanfalse指示是否要創建基于子類的(CGLIB)代理,而不是創建標準的基于Java接口的代理。

@Retryable

標注此注解的方法在發生異常時會進行重試

序號屬性類型默認值說明
1interceptorString""將 interceptor 的 bean 名稱應用到 retryable()
2valueClass[]{}可重試的異常類型。
3labelString""統計報告的唯一標簽。如果沒有提供,調用者可以選擇忽略它,或者提供默認值。
4maxAttemptsint3嘗試的最大次數(包括第一次失敗),默認為3次。
5backoff@Backoff@Backoff()指定用于重試此操作的backoff屬性。默認為空

@Backoff

序號屬性類型默認值說明
1delaylong0如果不設置則默認使用 1000 milliseconds重試等待
2maxDelaylong0最大重試等待時間
3multiplierlong0用于計算下一個延遲延遲的乘數(大于0生效)
4randombooleanfalse隨機重試等待時間

@Recover

用于恢復處理程序的方法調用的注釋。一個合適的復蘇handler有一個類型為可投擲(或可投擲的子類型)的第一個參數br/>和返回與`@Retryable`方法相同的類型的值。
可拋出的第一個參數是可選的(但是沒有它的方法只會被調用)。
從失敗方法的參數列表按順序填充后續的參數。

方法式使用

注解式只是讓我們使用更加便捷,但是如果要更高的靈活性。可以使用各種提供的方法。

  • SimpleDemo.java
public class SimpleDemo {private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDemo.class);public static void main(String[] args) throws Exception {RetryTemplate template = new RetryTemplate();// 策略SimpleRetryPolicy policy = new SimpleRetryPolicy();policy.setMaxAttempts(2);template.setRetryPolicy(policy);String result = template.execute(new RetryCallback<String, Exception>() {@Overridepublic String doWithRetry(RetryContext arg0) {throw new NullPointerException();}},new RecoveryCallback<String>() {@Overridepublic String recover(RetryContext context) {return "recovery callback";}});LOGGER.info("result: {}", result);}}
  • 執行日志
16:30:52.578 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=0 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=1 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=1 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=2 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry failed last attempt: count=2 16:30:52.592 [main] INFO com.github.houbb.retry.spring.commonway.SimpleDemo - result: recovery callback

spring-retry 結構

概覽

  • RetryCallback: 封裝你需要重試的業務邏輯(上文中的doSth)

  • RecoverCallback:封裝在多次重試都失敗后你需要執行的業務邏輯(上文中的doSthWhenStillFail)

  • RetryContext: 重試語境下的上下文,可用于在多次Retry或者Retry 和Recover之間傳遞參數或狀態(在多次doSth或者doSth與doSthWhenStillFail之間傳遞參數)

  • RetryOperations : 定義了“重試”的基本框架(模板),要求傳入RetryCallback,可選傳入RecoveryCallback;

  • RetryListener:典型的“監聽者”,在重試的不同階段通知“監聽者”(例如doSth,wait等階段時通知)

  • RetryPolicy : 重試的策略或條件,可以簡單的進行多次重試,可以是指定超時時間進行重試(上文中的someCondition)

  • BackOffPolicy: 重試的回退策略,在業務邏輯執行發生異常時。如果需要重試,我們可能需要等一段時間(可能服務器過于繁忙,如果一直不間隔重試可能拖垮服務器),
    當然這段時間可以是 0,也可以是固定的,可以是隨機的(參見tcp的擁塞控制算法中的回退策略)。回退策略在上文中體現為wait();

  • RetryTemplate: RetryOperations的具體實現,組合了RetryListener[],BackOffPolicy,RetryPolicy。

重試策略

  • NeverRetryPolicy:只允許調用RetryCallback一次,不允許重試

  • AlwaysRetryPolicy:允許無限重試,直到成功,此方式邏輯不當會導致死循環

  • SimpleRetryPolicy:固定次數重試策略,默認重試最大次數為3次,RetryTemplate默認使用的策略

  • TimeoutRetryPolicy:超時時間重試策略,默認超時時間為1秒,在指定的超時時間內允許重試

  • ExceptionClassifierRetryPolicy:設置不同異常的重試策略,類似組合重試策略,區別在于這里只區分不同異常的重試

  • CircuitBreakerRetryPolicy:有熔斷功能的重試策略,需設置3個參數openTimeout、resetTimeout和delegate

  • CompositeRetryPolicy:組合重試策略,有兩種組合方式,樂觀組合重試策略是指只要有一個策略允許重試即可以,
    悲觀組合重試策略是指只要有一個策略不允許重試即可以,但不管哪種組合方式,組合中的每一個策略都會執行

重試回退策略

重試回退策略,指的是每次重試是立即重試還是等待一段時間后重試。

默認情況下是立即重試,如果需要配置等待一段時間后重試則需要指定回退策略BackoffRetryPolicy。

  • NoBackOffPolicy:無退避算法策略,每次重試時立即重試

  • FixedBackOffPolicy:固定時間的退避策略,需設置參數sleeper和backOffPeriod,sleeper指定等待策略,默認是Thread.sleep,即線程休眠,backOffPeriod指定休眠時間,默認1秒

  • UniformRandomBackOffPolicy:隨機時間退避策略,需設置sleeper、minBackOffPeriod和maxBackOffPeriod,該策略在[minBackOffPeriod,maxBackOffPeriod之間取一個隨機休眠時間,minBackOffPeriod默認500毫秒,maxBackOffPeriod默認1500毫秒

  • ExponentialBackOffPolicy:指數退避策略,需設置參數sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠時間,默認100毫秒,maxInterval指定最大休眠時間,默認30秒,multiplier指定乘數,即下一次休眠時間為當前休眠時間*multiplier

  • ExponentialRandomBackOffPolicy:隨機指數退避策略,引入隨機乘數可以實現隨機乘數回退

guava-retrying

談話

小華:我們系統也要用到重試

項目經理:小明前段時間用了 spring-retry,分享下應該還不錯

小明:spring-retry 基本功能都有,但是必須是基于異常來進行控制。如果你要以返回值的某個狀態來判定是否需要重試,可能只能通過自己判斷返回值然后顯式拋出異常了。

小華:我們項目中想根據對象的屬性來進行重試。你可以看下 guava-retry,我很久以前用過,感覺還不錯。

小明:好的。

guava-retrying

guava-retrying?模塊提供了一種通用方法, 可以使用Guava謂詞匹配增強的特定停止、重試和異常處理功能來重試任意Java代碼。

  • 優勢

guava retryer工具與spring-retry類似,都是通過定義重試者角色來包裝正常邏輯重試,但是Guava retryer有更優的策略定義,在支持重試次數和重試頻度控制基礎上,能夠兼容支持多個異?;蛘咦远x實體對象的重試源定義,讓重試功能有更多的靈活性。

Guava Retryer也是線程安全的,入口調用邏輯采用的是?java.util.concurrent.Callable?的?call()?方法

代碼例子

入門案例

遇到異常之后,重試 3 次停止

  • HelloDemo.java
public static void main(String[] args) {Callable<Boolean> callable = new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {// do something useful hereLOGGER.info("call...");throw new RuntimeException();}};Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder().retryIfResult(Predicates.isNull()).retryIfExceptionOfType(IOException.class).retryIfRuntimeException().withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try {retryer.call(callable);} catch (RetryException | ExecutionException e) {e.printStackTrace();}}
  • 日志
2018-08-08 17:21:12.442 INFO [main] com.github.houbb.retry.guava.HelloDemo:41 - call... com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts. 2018-08-08 17:21:12.443 INFO [main] com.github.houbb.retry.guava.HelloDemo:41 - call... 2018-08-08 17:21:12.444 INFO [main] com.github.houbb.retry.guava.HelloDemo:41 - call...at com.github.rholder.retry.Retryer.call(Retryer.java:174)at com.github.houbb.retry.guava.HelloDemo.main(HelloDemo.java:53) Caused by: java.lang.RuntimeExceptionat com.github.houbb.retry.guava.HelloDemo$1.call(HelloDemo.java:42)at com.github.houbb.retry.guava.HelloDemo$1.call(HelloDemo.java:37)at com.github.rholder.retry.AttemptTimeLimiters$NoAttemptTimeLimit.call(AttemptTimeLimiters.java:78)at com.github.rholder.retry.Retryer.call(Retryer.java:160)... 1 more

重試策略

  • ExponentialBackoff.java

重試次數:3

重試策略:固定等待 3S

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder().retryIfResult(Predicates.isNull()).retryIfExceptionOfType(IOException.class).retryIfRuntimeException().withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try {retryer.call(callable);} catch (RetryException | ExecutionException e) {e.printStackTrace();}
  • 日志
2018-08-08 17:20:41.653 INFO [main] com.github.houbb.retry.guava.ExponentialBackoff:43 - call... 2018-08-08 17:20:44.659 INFO [main] com.github.houbb.retry.guava.ExponentialBackoff:43 - call... 2018-08-08 17:20:47.664 INFO [main] com.github.houbb.retry.guava.ExponentialBackoff:43 - call... com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts.at com.github.rholder.retry.Retryer.call(Retryer.java:174)at com.github.houbb.retry.guava.ExponentialBackoff.main(ExponentialBackoff.java:56) Caused by: java.lang.RuntimeExceptionat com.github.houbb.retry.guava.ExponentialBackoff$1.call(ExponentialBackoff.java:44)at com.github.houbb.retry.guava.ExponentialBackoff$1.call(ExponentialBackoff.java:39)at com.github.rholder.retry.AttemptTimeLimiters$NoAttemptTimeLimit.call(AttemptTimeLimiters.java:78)at com.github.rholder.retry.Retryer.call(Retryer.java:160)... 1 more

guava-retrying 簡介

RetryerBuilder

RetryerBuilder 是一個 factory 創建者,可以定制設置重試源且可以支持多個重試源,可以配置重試次數或重試超時時間,以及可以配置等待時間間隔,創建重試者 Retryer 實例。

RetryerBuilder 的重試源支持 Exception 異常對象和自定義斷言對象,通過retryIfException 和 retryIfResult 設置,同時支持多個且能兼容。

  • retryIfException

retryIfException,拋出 runtime 異常、checked 異常時都會重試,但是拋出 error 不會重試。

  • retryIfRuntimeException

retryIfRuntimeException 只會在拋 runtime 異常的時候才重試,checked 異常和error 都不重試。

  • retryIfExceptionOfType

retryIfExceptionOfType 允許我們只在發生特定異常的時候才重試,比如NullPointerException 和 IllegalStateException 都屬于 runtime 異常,也包括自定義的error。

如:  

retryIfExceptionOfType(Error.class)// 只在拋出error重試

當然我們還可以在只有出現指定的異常的時候才重試,如: 

```java 
.retryIfExceptionOfType(IllegalStateException.class)
.retryIfExceptionOfType(NullPointerException.class)

或者通過Predicate實現```java .retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class), Predicates.instanceOf(IllegalStateException.class)))
  • retryIfResult

retryIfResult 可以指定你的 Callable 方法在返回值的時候進行重試,如  

// 返回false重試 .retryIfResult(Predicates.equalTo(false)) //以_error結尾才重試 .retryIfResult(Predicates.containsPattern("_error$"))
  • RetryListener

當發生重試之后,假如我們需要做一些額外的處理動作,比如log一下異常,那么可以使用RetryListener。

每次重試之后,guava-retrying 會自動回調我們注冊的監聽。

可以注冊多個RetryListener,會按照注冊順序依次調用。  

.withRetryListener(new RetryListener { @Override public <T> void onRetry(Attempt<T> attempt) { logger.error("第【{}】次調用失敗" , attempt.getAttemptNumber()); } } )

主要接口

序號接口描述備注
1Attempt一次執行任務?
2AttemptTimeLimiter單次任務執行時間限制如果單次任務執行超時,則終止執行當前任務
3BlockStrategies任務阻塞策略通俗的講就是當前任務執行完,下次任務還沒開始這段時間做什么),默認策略為:BlockStrategies.THREAD_SLEEP_STRATEGY
4RetryException重試異常?
5RetryListener自定義重試監聽器可以用于異步記錄錯誤日志
6StopStrategy停止重試策略?
7WaitStrategy等待時長策略(控制時間間隔),返回結果為下次執行時長
8Attempt一次執行任務
9Attempt一次執行任務

StopStrategy

提供三種:

  • StopAfterDelayStrategy

設定一個最長允許的執行時間;比如設定最長執行10s,無論任務執行次數,只要重試的時候超出了最長時間,則任務終止,并返回重試異常RetryException;

  • NeverStopStrategy

不停止,用于需要一直輪訓知道返回期望結果的情況;

  • StopAfterAttemptStrategy

設定最大重試次數,如果超出最大重試次數則停止重試,并返回重試異常;

WaitStrategy

  • FixedWaitStrategy

固定等待時長策略;

  • RandomWaitStrategy

隨機等待時長策略(可以提供一個最小和最大時長,等待時長為其區間隨機值)

  • IncrementingWaitStrategy

遞增等待時長策略(提供一個初始值和步長,等待時間隨重試次數增加而增加)

  • ExponentialWaitStrategy

指數等待時長策略;

  • FibonacciWaitStrategy

Fibonacci 等待時長策略;

  • ExceptionWaitStrategy

異常時長等待策略;

  • CompositeWaitStrategy

復合時長等待策略;

總結

優雅重試共性和原理

正常和重試優雅解耦,重試斷言條件實例或邏輯異常實例是兩者溝通的媒介。

約定重試間隔,差異性重試策略,設置重試超時時間,進一步保證重試有效性以及重試流程穩定性。

都使用了命令設計模式,通過委托重試對象完成相應的邏輯操作,同時內部封裝實現重試邏輯。

spring-retry 和 guava-retry 工具都是線程安全的重試,能夠支持并發業務場景的重試邏輯正確性。

優雅重試適用場景

功能邏輯中存在不穩定依賴場景,需要使用重試獲取預期結果或者嘗試重新執行邏輯不立即結束。比如遠程接口訪問,數據加載訪問,數據上傳校驗等等。

對于異常場景存在需要重試場景,同時希望把正常邏輯和重試邏輯解耦。

對于需要基于數據媒介交互,希望通過重試輪詢檢測執行邏輯場景也可以考慮重試方案。

談話

項目經理:我覺得 guava-retry 挺好的,就是不夠方便。小明啊,你給封裝個基于注解的吧。

小明:……

總結

以上是生活随笔為你收集整理的java retry(重试) spring retry, guava retrying 详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。