小明同窗與產品經理的鬥智鬥勇過程,當接口有時候異常想重試,你會怎麼辦?隨着需求的不斷提出,怎麼去迭代升級,看看這篇文章,寫得很好!!java
必定要看完,哈哈!而後點個贊。git
做者:葉止水
java retry 的一步步實現機制。web
產品經理:實現一個按條件,查詢用戶信息的服務。面試
小明:好的。沒問題。算法
public interface UserService { /** * 根據條件查詢用戶信息 * @param condition 條件 * @return User 信息 */ User queryUser(QueryUserCondition condition); }
public class UserServiceImpl implements UserService { private OutService outService; public UserServiceImpl(OutService outService) { this.outService = outService; } @Override public User queryUser(QueryUserCondition condition) { outService.remoteCall(); return new User(); } }
項目經理:這個服務有時候會失敗,你看下。spring
小明:OutService
在是一個 RPC 的外部服務,可是有時候不穩定。編程
項目經理:若是調用失敗了,你能夠調用的時候重試幾回。你去看下重試相關的東西後端
對於重試是有場景限制的,不是什麼場景都適合重試,好比參數校驗不合法、寫操做等(要考慮寫是否冪等)都不適合重試。設計模式
遠程調用超時、網絡忽然中斷能夠重試。在微服務治理框架中,一般都有本身的重試與超時配置,好比dubbo能夠設置retries=1,timeout=500調用失敗只重試1次,超過500ms調用仍未返回則調用失敗。
好比外部 RPC 調用,或者數據入庫等操做,若是一次操做失敗,能夠進行屢次重試,提升調用成功的可能性。
小明:我手頭還有其餘任務,這個也挺簡單的。5 分鐘時間搞定他。
public class UserServiceRetryImpl implements UserService { @Override public 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; } }
項目經理:你的代碼我看了,功能雖然實現了,可是儘可能寫的易於維護一點。
小明:好的。(心想,是說要寫點註釋什麼的?)
爲其餘對象提供一種代理以控制對這個對象的訪問。
在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在客戶端和目標對象之間起到中介做用。
其特徵是代理與委託類有一樣的接口。
小明想到之前看過的代理模式,心想用這種方式,原來的代碼改動量較少,之後想改起來也方便些。
public class UserServiceProxyImpl implements UserService { private UserService userService = new UserServiceImpl(); @Override public 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; } }
項目經理:小明啊,這裏還有個方法也是一樣的問題。你也給加上重試吧。
小明:好的。
小明心想,我在寫一個代理,可是轉念冷靜了下來,若是還有個服務也要重試怎麼辦呢?
public interface RoleService { /** * 查詢 * @param user 用戶信息 * @return 是否擁有權限 */ boolean hasPrivilege(User user); }
public class DynamicProxy implements InvocationHandler { private final Object subject; public DynamicProxy(Object subject) { this.subject = subject; } @Override public 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); }
項目經理:小明,你動態代理的方式是挺會偷懶的,但是咱們有的類沒有接口。這個問題你要解決一下。
小明:好的。(誰?寫服務居然不定義接口)
public class ResourceServiceImpl { /** * 校驗資源信息 * @param user 入參 * @return 是否校驗經過 */ public boolean checkResource(User user) { OutService outService = new AlwaysFailOutServiceImpl(); outService.remoteCall(); return true; } }
小明看了下網上的資料,解決的辦法仍是有的。
CGLIB 是一個功能強大、高性能和高質量的代碼生成庫,用於擴展JAVA類並在運行時實現接口。
javassist (Java編程助手)使Java字節碼操做變得簡單。
它是Java中編輯字節碼的類庫;它容許Java程序在運行時定義新類,並在JVM加載類文件時修改類文件。
與其餘相似的字節碼編輯器不一樣,Javassist提供了兩個級別的API:源級和字節碼級。
若是用戶使用源代碼級API,他們能夠編輯類文件,而不須要了解Java字節碼的規範。
整個API只使用Java語言的詞彙表進行設計。您甚至能夠以源文本的形式指定插入的字節碼;Javassist動態編譯它。
另外一方面,字節碼級API容許用戶直接編輯類文件做爲其餘編輯器。
ASM 是一個通用的Java字節碼操做和分析框架。
它能夠用來修改現有的類或動態地生成類,直接以二進制形式。
ASM提供了一些通用的字節碼轉換和分析算法,能夠從這些算法中構建自定義複雜的轉換和代碼分析工具。
ASM提供與其餘Java字節碼框架相似的功能,但主要關注性能。
由於它的設計和實現都儘量地小和快,因此很是適合在動態系統中使用(固然也能夠以靜態的方式使用,例如在編譯器中)。
小明看了下,就選擇使用 CGLIB。
public class CglibProxy implements MethodInterceptor { @Override public 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); }
項目經理:小明啊,最近我在想一個問題。不一樣的服務,重試的時候次數應該是不一樣的。由於服務對穩定性的要求各不相同啊。
小明:好的。(心想,重試都搞了一週了,今天都週五了。)
下班以前,小明一直在想這個問題。恰好週末,花點時間寫個重試小工具吧。
spring
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; }
@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); } } }
當前方法一共重試 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 ...
週一來到公司,項目經理又和小明談了起來。
項目經理:重試次數是知足了,可是重試其實應該講究策略。好比調用外部,第一次失敗,能夠等待 5S 在次調用,若是又失敗了,能夠等待 10S 再調用。。。
小明:瞭解。
但是今天週一,還有其餘不少事情要作。
小明在想,沒時間寫這個呀。看看網上有沒有現成的。
Spring Retry 爲 Spring 應用程序提供了聲明性重試支持。 它用於Spring批處理、Spring集成、Apache Hadoop(等等)的Spring。
在分佈式系統中,爲了保證數據分佈式事務的強一致性,你們在調用RPC接口或者發送MQ時,針對可能會出現網絡抖動請求超時狀況採起一下重試操做。 你們用的最多的重試方式就是MQ了,可是若是你的項目中沒有引入MQ,那就不方便了。
還有一種方式,是開發者本身編寫重試機制,可是大多不夠優雅。
重試條件:遇到 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 異常 */ @Recover public 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 { @Autowired private RemoteService remoteService; @Test public 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
註解在使用時沒法指定方法,若是一個類中多個重試方法,就會很麻煩。
表示是否開始重試。
序號 | 屬性 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
1 | proxyTargetClass | boolean | false | 指示是否要建立基於子類的(CGLIB)代理,而不是建立標準的基於Java接口的代理。 |
標註此註解的方法在發生異常時會進行重試
序號 | 屬性 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
1 | interceptor | String | "" | 將 interceptor 的 bean 名稱應用到 retryable() |
2 | value | Class[] | {} | 可重試的異常類型。 |
3 | label | String | "" | 統計報告的惟一標籤。若是沒有提供,調用者能夠選擇忽略它,或者提供默認值。 |
4 | maxAttempts | int | 3 | 嘗試的最大次數(包括第一次失敗),默認爲3次。 |
5 | backoff | @Backoff | @Backoff() | 指定用於重試此操做的backoff屬性。默認爲空 |
序號 | 屬性 | 類型 | 默認值 | 說明 | |
---|---|---|---|---|---|
1 | delay | long | 0 | 若是不設置則默認使用 1000 milliseconds | 重試等待 |
2 | maxDelay | long | 0 | 最大重試等待時間 | |
3 | multiplier | long | 0 | 用於計算下一個延遲延遲的乘數(大於0生效) | |
4 | random | boolean | false | 隨機重試等待時間 |
用於恢復處理程序的方法調用的註釋。一個合適的復甦handler有一個類型爲可投擲(或可投擲的子類型)的第一個參數[br/>和返回與@Retryable
方法相同的類型的值。
可拋出的第一個參數是可選的(可是沒有它的方法只會被調用)。
從失敗方法的參數列表按順序填充後續的參數。和返回與@Retryable
方法相同的類型的值。
註解式只是讓咱們使用更加便捷,可是若是要更高的靈活性。可使用各類提供的方法。
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>() { @Override public String doWithRetry(RetryContext arg0) { throw new NullPointerException(); } } , new RecoveryCallback<String>() { @Override public 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
重試回退策略,指的是每次重試是當即重試仍是等待一段時間後重試。
默認狀況下是當即重試,若是須要配置等待一段時間後重試則須要指定回退策略BackoffRetryPolicy。
小華:咱們系統也要用到重試
項目經理:小明前段時間用了 spring-retry,分享下應該還不錯
小明:spring-retry 基本功能都有,可是必須是基於異常來進行控制。若是你要以返回值的某個狀態來斷定是否須要重試,可能只能經過本身判斷返回值而後顯式拋出異常了。
小華:咱們項目中想根據對象的屬性來進行重試。你能夠看下 guava-retry,我好久之前用過,感受還不錯。
小明:好的。
](mailto:br/>和返回與@Retryable
方法相同的類型的值。<br/)
[](mailto:br/>和返回與@Retryable
方法相同的類型的值。<br/)guava-retrying 模塊提供了一種通用方法, 可使用Guava謂詞匹配加強的特定中止、重試和異常處理功能來重試任意Java代碼。
guava retryer工具與spring-retry相似,都是經過定義重試者角色來包裝正常邏輯重試,可是Guava retryer有更優的策略定義,在支持重試次數和重試頻度控制基礎上,可以兼容支持多個異常或者自定義實體對象的重試源定義,讓重試功能有更多的靈活性。
Guava Retryer也是線程安全的,入口調用邏輯採用的是 java.util.concurrent.Callable
的 call()
方法
遇到異常以後,重試 3 次中止
public static void main(String[] args) { Callable<Boolean> callable = new Callable<Boolean>() { @Override public Boolean call() throws Exception { // do something useful here LOGGER.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.RuntimeException at 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
重試次數: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.RuntimeException at 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
RetryerBuilder 是一個 factory 建立者,能夠定製設置重試源且能夠支持多個重試源,能夠配置重試次數或重試超時時間,以及能夠配置等待時間間隔,建立重試者 Retryer 實例。
RetryerBuilder 的重試源支持 Exception 異常對象和自定義斷言對象,經過retryIfException 和 retryIfResult 設置,同時支持多個且能兼容。
retryIfException,拋出 runtime 異常、checked 異常時都會重試,可是拋出 error 不會重試。
retryIfRuntimeException 只會在拋 runtime 異常的時候才重試,checked 異常和error 都不重試。
retryIfExceptionOfType 容許咱們只在發生特定異常的時候才重試,好比NullPointerException 和 IllegalStateException 都屬於 runtime 異常,也包括自定義的error。
如:
retryIfExceptionOfType(Error.class)// 只在拋出error重試
固然咱們還能夠在只有出現指定的異常的時候才重試,如:
.retryIfExceptionOfType(IllegalStateException.class)\ .retryIfExceptionOfType(NullPointerException.class)
或者經過Predicate實現
.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class), Predicates.instanceOf(IllegalStateException.class)))
retryIfResult 能夠指定你的 Callable 方法在返回值的時候進行重試,如
// 返回false重試 .retryIfResult(Predicates.equalTo(false)) //以_error結尾才重試 .retryIfResult(Predicates.containsPattern("_error$"))
當發生重試以後,假如咱們須要作一些額外的處理動做,好比log一下異常,那麼可使用RetryListener。
每次重試以後,guava-retrying 會自動回調咱們註冊的監聽。
能夠註冊多個RetryListener,會按照註冊順序依次調用。
.withRetryListener(new RetryListener { @Override public <T> void onRetry(Attempt<T> attempt) { logger.error("第【{}】次調用失敗" , attempt.getAttemptNumber()); } } )
序號 | 接口 | 描述 | 備註 |
---|---|---|---|
1 | Attempt | 一次執行任務 | |
2 | AttemptTimeLimiter | 單次任務執行時間限制 | 若是單次任務執行超時,則終止執行當前任務 |
3 | BlockStrategies | 任務阻塞策略 | 通俗的講就是當前任務執行完,下次任務還沒開始這段時間作什麼),默認策略爲:BlockStrategies.THREAD_SLEEP_STRATEGY |
4 | RetryException | 重試異常 | |
5 | RetryListener | 自定義重試監聽器 | 能夠用於異步記錄錯誤日誌 |
6 | StopStrategy | 中止重試策略 | |
7 | WaitStrategy | 等待時長策略 | (控制時間間隔),返回結果爲下次執行時長 |
8 | Attempt | 一次執行任務 | |
9 | Attempt | 一次執行任務 |
StopStrategy
提供三種:
設定一個最長容許的執行時間;好比設定最長執行10s,不管任務執行次數,只要重試的時候超出了最長時間,則任務終止,並返回重試異常RetryException;
不中止,用於須要一直輪訓知道返回指望結果的狀況;
設定最大重試次數,若是超出最大重試次數則中止重試,並返回重試異常;
WaitStrategy
固定等待時長策略;
隨機等待時長策略(能夠提供一個最小和最大時長,等待時長爲其區間隨機值)
遞增等待時長策略(提供一個初始值和步長,等待時間隨重試次數增長而增長)
指數等待時長策略;
Fibonacci 等待時長策略;
異常時長等待策略;
複合時長等待策略;
正常和重試優雅解耦,重試斷言條件實例或邏輯異常實例是二者溝通的媒介。
約定重試間隔,差別性重試策略,設置重試超時時間,進一步保證重試有效性以及重試流程穩定性。
都使用了命令設計模式,經過委託重試對象完成相應的邏輯操做,同時內部封裝實現重試邏輯。
spring-retry 和 guava-retry 工具都是線程安全的重試,可以支持併發業務場景的重試邏輯正確性。
功能邏輯中存在不穩定依賴場景,須要使用重試獲取預期結果或者嘗試從新執行邏輯不當即結束。好比遠程接口訪問,數據加載訪問,數據上傳校驗等等。
對於異常場景存在須要重試場景,同時但願把正常邏輯和重試邏輯解耦。
對於須要基於數據媒介交互,但願經過重試輪詢檢測執行邏輯場景也能夠考慮重試方案。
項目經理:我以爲 guava-retry 挺好的,就是不夠方便。小明啊,你給封裝個基於註解的吧。
小明:......
java 重試框架------sisyphus:https://github.com/houbb/sisy...
推薦閱讀:
太讚了,SpringBoot+Vue先後端分離完整入門教程!
分享一套SpringBoot開發博客系統源碼,以及完整開發文檔!速度保存!