国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

java retry(重試) spring retry, guava retrying 詳解

LucasTwilight / 1186人閱讀

摘要:支持重試版本思考小明我手頭還有其他任務(wù),這個(gè)也挺簡(jiǎn)單的。與其他類似的字節(jié)碼編輯器不同,提供了兩個(gè)級(jí)別的源級(jí)和字節(jié)碼級(jí)。另一方面,字節(jié)碼級(jí)允許用戶直接編輯類文件作為其他編輯器。提供與其他字節(jié)碼框架類似的功能,但主要關(guān)注性能。

系列說(shuō)明

java retry 的一步步實(shí)現(xiàn)機(jī)制。

java-retry 源碼地址
情景導(dǎo)入 簡(jiǎn)單的需求

產(chǎn)品經(jīng)理:實(shí)現(xiàn)一個(gè)按條件,查詢用戶信息的服務(wù)。

小明:好的。沒(méi)問(wèn)題。

代碼

UserService.java

public interface UserService {

    /**
     * 根據(jù)條件查詢用戶信息
     * @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;
    }

    @Override
    public User queryUser(QueryUserCondition condition) {
        outService.remoteCall();
        return new User();
    }

}
談話

項(xiàng)目經(jīng)理:這個(gè)服務(wù)有時(shí)候會(huì)失敗,你看下。

小明:OutService 在是一個(gè) RPC 的外部服務(wù),但是有時(shí)候不穩(wěn)定。

項(xiàng)目經(jīng)理:如果調(diào)用失敗了,你可以調(diào)用的時(shí)候重試幾次。你去看下重試相關(guān)的東西

重試 重試作用

對(duì)于重試是有場(chǎng)景限制的,不是什么場(chǎng)景都適合重試,比如參數(shù)校驗(yàn)不合法、寫(xiě)操作等(要考慮寫(xiě)是否冪等)都不適合重試。

遠(yuǎn)程調(diào)用超時(shí)、網(wǎng)絡(luò)突然中斷可以重試。在微服務(wù)治理框架中,通常都有自己的重試與超時(shí)配置,比如dubbo可以設(shè)置retries=1,timeout=500調(diào)用失敗只重試1次,超過(guò)500ms調(diào)用仍未返回則調(diào)用失敗。

比如外部 RPC 調(diào)用,或者數(shù)據(jù)入庫(kù)等操作,如果一次操作失敗,可以進(jìn)行多次重試,提高調(diào)用成功的可能性

V1.0 支持重試版本 思考

小明:我手頭還有其他任務(wù),這個(gè)也挺簡(jiǎn)單的。5 分鐘時(shí)間搞定他。

實(shí)現(xiàn)

UserServiceRetryImpl.java

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;
    }

}
V1.1 代理模式版本 易于維護(hù)

項(xiàng)目經(jīng)理:你的代碼我看了,功能雖然實(shí)現(xiàn)了,但是盡量寫(xiě)的易于維護(hù)一點(diǎn)。

小明:好的。(心想,是說(shuō)要寫(xiě)點(diǎn)注釋什么的?)

代理模式

為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。

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

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

實(shí)現(xiàn)

小明想到以前看過(guò)的代理模式,心想用這種方式,原來(lái)的代碼改動(dòng)量較少,以后想改起來(lái)也方便些

UserServiceProxyImpl.java

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;
    }

}
V1.2 動(dòng)態(tài)代理模式 方便拓展

項(xiàng)目經(jīng)理:小明啊,這里還有個(gè)方法也是同樣的問(wèn)題。你也給加上重試吧。

小明:好的。

小明心想,我在寫(xiě)一個(gè)代理,但是轉(zhuǎn)念冷靜了下來(lái),如果還有個(gè)服務(wù)也要重試怎么辦呢?

RoleService.java

public interface RoleService {

    /**
     * 查詢
     * @param user 用戶信息
     * @return 是否擁有權(quán)限
     */
    boolean hasPrivilege(User user);

}
代碼實(shí)現(xiàn)

DynamicProxy.java

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 {
                // 當(dāng)代理對(duì)象調(diào)用真實(shí)對(duì)象的方法時(shí),其會(huì)自動(dòng)的跳轉(zhuǎn)到代理對(duì)象關(guān)聯(lián)的handler對(duì)象的invoke方法來(lái)進(jìn)行調(diào)用
                return method.invoke(subject, args);
            } catch (Exception e) {
                times++;

                if (times >= RetryConstant.MAX_TIMES) {
                    throw new RuntimeException(e);
                }
            }
        }

        return null;
    }

    /**
     * 獲取動(dòng)態(tài)代理
     *
     * @param realSubject 代理對(duì)象
     */
    public static Object getProxy(Object realSubject) {
        //    我們要代理哪個(gè)真實(shí)對(duì)象,就將該對(duì)象傳進(jìn)去,最后是通過(guò)該真實(shí)對(duì)象來(lái)調(diào)用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);
        return Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);
    }

}

測(cè)試代碼

@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 動(dòng)態(tài)代理模式增強(qiáng) 對(duì)話

項(xiàng)目經(jīng)理:小明,你動(dòng)態(tài)代理的方式是挺會(huì)偷懶的,可是我們有的類沒(méi)有接口。這個(gè)問(wèn)題你要解決一下。

小明:好的。(誰(shuí)?寫(xiě)服務(wù)竟然不定義接口)

ResourceServiceImpl.java

public class ResourceServiceImpl {

    /**
     * 校驗(yàn)資源信息
     * @param user 入?yún)?     * @return 是否校驗(yàn)通過(guò)
     */
    public boolean checkResource(User user) {
        OutService outService = new AlwaysFailOutServiceImpl();
        outService.remoteCall();
        return true;
    }

}
字節(jié)碼技術(shù)

小明看了下網(wǎng)上的資料,解決的辦法還是有的。

CGLIB

CGLIB 是一個(gè)功能強(qiáng)大、高性能和高質(zhì)量的代碼生成庫(kù),用于擴(kuò)展JAVA類并在運(yùn)行時(shí)實(shí)現(xiàn)接口。

javassist

javassist (Java編程助手)使Java字節(jié)碼操作變得簡(jiǎn)單。

它是Java中編輯字節(jié)碼的類庫(kù);它允許Java程序在運(yùn)行時(shí)定義新類,并在JVM加載類文件時(shí)修改類文件。

與其他類似的字節(jié)碼編輯器不同,Javassist提供了兩個(gè)級(jí)別的API:源級(jí)和字節(jié)碼級(jí)。

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

整個(gè)API只使用Java語(yǔ)言的詞匯表進(jìn)行設(shè)計(jì)。您甚至可以以源文本的形式指定插入的字節(jié)碼;Javassist動(dòng)態(tài)編譯它。

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

ASM

ASM 是一個(gè)通用的Java字節(jié)碼操作和分析框架。

它可以用來(lái)修改現(xiàn)有的類或動(dòng)態(tài)地生成類,直接以二進(jìn)制形式。

ASM提供了一些通用的字節(jié)碼轉(zhuǎn)換和分析算法,可以從這些算法中構(gòu)建自定義復(fù)雜的轉(zhuǎn)換和代碼分析工具。

ASM提供與其他Java字節(jié)碼框架類似的功能,但主要關(guān)注性能。

因?yàn)樗脑O(shè)計(jì)和實(shí)現(xiàn)都盡可能地小和快,所以非常適合在動(dòng)態(tài)系統(tǒng)中使用(當(dāng)然也可以以靜態(tài)的方式使用,例如在編譯器中)。

實(shí)現(xiàn)

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

CglibProxy.java

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 {
                //通過(guò)代理子類調(diào)用父類的方法
                return methodProxy.invokeSuper(o, objects);
            } catch (Exception e) {
                times++;

                if (times >= RetryConstant.MAX_TIMES) {
                    throw new RuntimeException(e);
                }
            }
        }

        return null;
    }

    /**
     * 獲取代理類
     * @param clazz 類信息
     * @return 代理類結(jié)果
     */
    public Object getProxy(Class clazz){
        Enhancer enhancer = new Enhancer();
        //目標(biāo)對(duì)象類
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通過(guò)字節(jié)碼技術(shù)創(chuàng)建目標(biāo)對(duì)象類的子類實(shí)例作為代理
        return enhancer.create();
    }

}

測(cè)試

@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 實(shí)現(xiàn) 對(duì)話

項(xiàng)目經(jīng)理:小明啊,最近我在想一個(gè)問(wèn)題。不同的服務(wù),重試的時(shí)候次數(shù)應(yīng)該是不同的。因?yàn)榉?wù)對(duì)穩(wěn)定性的要求各不相同啊。

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

下班之前,小明一直在想這個(gè)問(wèn)題。剛好周末,花點(diǎn)時(shí)間寫(xiě)個(gè)重試小工具吧。

設(shè)計(jì)思路

技術(shù)支持

spring

java 注解

注解定義

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

注解解析

攔截指定需要重試的方法,解析對(duì)應(yīng)的重試次數(shù),然后進(jìn)行對(duì)應(yīng)次數(shù)的重試。

實(shí)現(xiàn)

Retryable.java

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

    /**
     * Exception type that are retryable.
     * @return exception type to retry
     */
    Class 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. 最大次數(shù)判斷
        int maxAttempts = retryable.maxAttempts();
        if (maxAttempts <= 1) {
            return point.proceed();
        }

        //2. 異常處理
        int times = 0;
        final Class exceptionClass = retryable.value();
        while (times < maxAttempts) {
            try {
                return point.proceed();
            } catch (Throwable e) {
                times++;

                // 超過(guò)最大重試次數(shù) or 不屬于當(dāng)前處理異常
                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()

當(dāng)前方法一共重試 5 次。
重試條件:服務(wù)拋出 AopRuntimeExption

@Override
@Retryable(maxAttempts = 5, value = AopRuntimeExption.class)
public void fiveTimes() {
    LOGGER.info("fiveTimes called!");
    throw new AopRuntimeExption();
}

測(cè)試日志

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 版本 對(duì)話

周一來(lái)到公司,項(xiàng)目經(jīng)理又和小明談了起來(lái)。

項(xiàng)目經(jīng)理:重試次數(shù)是滿足了,但是重試其實(shí)應(yīng)該講究策略。比如調(diào)用外部,第一次失敗,可以等待 5S 在次調(diào)用,如果又失敗了,可以等待 10S 再調(diào)用。。。

小明:了解。

思考

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

小明在想,沒(méi)時(shí)間寫(xiě)這個(gè)呀。看看網(wǎng)上有沒(méi)有現(xiàn)成的。

spring-retry

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

在分布式系統(tǒng)中,為了保證數(shù)據(jù)分布式事務(wù)的強(qiáng)一致性,大家在調(diào)用RPC接口或者發(fā)送MQ時(shí),針對(duì)可能會(huì)出現(xiàn)網(wǎng)絡(luò)抖動(dòng)請(qǐng)求超時(shí)情況采取一下重試操作。 大家用的最多的重試方式就是MQ了,但是如果你的項(xiàng)目中沒(méi)有引入MQ,那就不方便了。

還有一種方式,是開(kāi)發(fā)者自己編寫(xiě)重試機(jī)制,但是大多不夠優(yōu)雅。

注解式使用

RemoteService.java

重試條件:遇到 RuntimeException

重試次數(shù):3

重試策略:重試的時(shí)候等待 5S, 后面時(shí)間依次變?yōu)樵瓉?lái)的 2 倍數(shù)。

熔斷機(jī)制:全部重試失敗,則調(diào)用 recover() 方法。

@Service
public class RemoteService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);

    /**
     * 調(diào)用方法
     */
    @Retryable(value = RuntimeException.class,
               maxAttempts = 3,
               backoff = @Backoff(delay = 5000L, multiplier = 2))
    public void call() {
        LOGGER.info("Call something...");
        throw new RuntimeException("RPC調(diào)用異常");
    }

    /**
     * recover 機(jī)制
     * @param e 異常
     */
    @Recover
    public void recover(RuntimeException e) {
        LOGGER.info("Start do recover things....");
        LOGGER.warn("We meet ex: ", e);
    }

}

測(cè)試

@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調(diào)用異常
    at com.github.houbb.retry.spring.service.RemoteService.call(RemoteService.java:38) ~[classes/:na]
...

三次調(diào)用的時(shí)間點(diǎn):

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

spring-retry 工具雖能優(yōu)雅實(shí)現(xiàn)重試,但是存在兩個(gè)不友好設(shè)計(jì):

一個(gè)是重試實(shí)體限定為 Throwable 子類,說(shuō)明重試針對(duì)的是可捕捉的功能異常為設(shè)計(jì)前提的,但是我們希望依賴某個(gè)數(shù)據(jù)對(duì)象實(shí)體作為重試實(shí)體,
但 sping-retry框架必須強(qiáng)制轉(zhuǎn)換為T(mén)hrowable子類。

另一個(gè)就是重試根源的斷言對(duì)象使用的是 doWithRetry 的 Exception 異常實(shí)例,不符合正常內(nèi)部斷言的返回設(shè)計(jì)。

Spring Retry 提倡以注解的方式對(duì)方法進(jìn)行重試,重試邏輯是同步執(zhí)行的,重試的“失敗”針對(duì)的是Throwable,
如果你要以返回值的某個(gè)狀態(tài)來(lái)判定是否需要重試,可能只能通過(guò)自己判斷返回值然后顯式拋出異常了。

@Recover 注解在使用時(shí)無(wú)法指定方法,如果一個(gè)類中多個(gè)重試方法,就會(huì)很麻煩。

注解介紹 @EnableRetry

表示是否開(kāi)始重試。

序號(hào) 屬性 類型 默認(rèn)值 說(shuō)明
1 proxyTargetClass boolean false 指示是否要?jiǎng)?chuàng)建基于子類的(CGLIB)代理,而不是創(chuàng)建標(biāo)準(zhǔn)的基于Java接口的代理。
@Retryable

標(biāo)注此注解的方法在發(fā)生異常時(shí)會(huì)進(jìn)行重試

序號(hào) 屬性 類型 默認(rèn)值 說(shuō)明
1 interceptor String "" 將 interceptor 的 bean 名稱應(yīng)用到 retryable()
2 value Class[] {} 可重試的異常類型。
3 label String "" 統(tǒng)計(jì)報(bào)告的唯一標(biāo)簽。如果沒(méi)有提供,調(diào)用者可以選擇忽略它,或者提供默認(rèn)值。
4 maxAttempts int 3 嘗試的最大次數(shù)(包括第一次失敗),默認(rèn)為3次。
5 backoff @Backoff @Backoff() 指定用于重試此操作的backoff屬性。默認(rèn)為空
@Backoff
序號(hào) 屬性 類型 默認(rèn)值 說(shuō)明
1 delay long 0 如果不設(shè)置則默認(rèn)使用 1000 milliseconds 重試等待
2 maxDelay long 0 最大重試等待時(shí)間
3 multiplier long 0 用于計(jì)算下一個(gè)延遲延遲的乘數(shù)(大于0生效)
4 random boolean false 隨機(jī)重試等待時(shí)間
@Recover

用于恢復(fù)處理程序的方法調(diào)用的注釋。一個(gè)合適的復(fù)蘇handler有一個(gè)類型為可投擲(或可投擲的子類型)的第一個(gè)參數(shù)
和返回與@Retryable方法相同的類型的值。
可拋出的第一個(gè)參數(shù)是可選的(但是沒(méi)有它的方法只會(huì)被調(diào)用)。
從失敗方法的參數(shù)列表按順序填充后續(xù)的參數(shù)。

方法式使用

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

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() {
                    @Override
                    public String doWithRetry(RetryContext arg0) {
                        throw new NullPointerException();
                    }
                }
                ,
                new RecoveryCallback() {
                    @Override
                    public String recover(RetryContext context) {
                        return "recovery callback";
                    }
                }
        );

        LOGGER.info("result: {}", result);
    }

}

執(zhí)行日志

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 結(jié)構(gòu)

概覽

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

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

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

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

RetryListener:典型的“監(jiān)聽(tīng)者”,在重試的不同階段通知“監(jiān)聽(tīng)者”(例如doSth,wait等階段時(shí)通知)

RetryPolicy : 重試的策略或條件,可以簡(jiǎn)單的進(jìn)行多次重試,可以是指定超時(shí)時(shí)間進(jìn)行重試(上文中的someCondition)

BackOffPolicy: 重試的回退策略,在業(yè)務(wù)邏輯執(zhí)行發(fā)生異常時(shí)。如果需要重試,我們可能需要等一段時(shí)間(可能服務(wù)器過(guò)于繁忙,如果一直不間隔重試可能拖垮服務(wù)器),

當(dāng)然這段時(shí)間可以是 0,也可以是固定的,可以是隨機(jī)的(參見(jiàn)tcp的擁塞控制算法中的回退策略)。回退策略在上文中體現(xiàn)為wait();

RetryTemplate: RetryOperations的具體實(shí)現(xiàn),組合了RetryListener[],BackOffPolicy,RetryPolicy。

重試策略

NeverRetryPolicy:只允許調(diào)用RetryCallback一次,不允許重試

AlwaysRetryPolicy:允許無(wú)限重試,直到成功,此方式邏輯不當(dāng)會(huì)導(dǎo)致死循環(huán)

SimpleRetryPolicy:固定次數(shù)重試策略,默認(rèn)重試最大次數(shù)為3次,RetryTemplate默認(rèn)使用的策略

TimeoutRetryPolicy:超時(shí)時(shí)間重試策略,默認(rèn)超時(shí)時(shí)間為1秒,在指定的超時(shí)時(shí)間內(nèi)允許重試

ExceptionClassifierRetryPolicy:設(shè)置不同異常的重試策略,類似組合重試策略,區(qū)別在于這里只區(qū)分不同異常的重試

CircuitBreakerRetryPolicy:有熔斷功能的重試策略,需設(shè)置3個(gè)參數(shù)openTimeout、resetTimeout和delegate

CompositeRetryPolicy:組合重試策略,有兩種組合方式,樂(lè)觀組合重試策略是指只要有一個(gè)策略允許重試即可以,

悲觀組合重試策略是指只要有一個(gè)策略不允許重試即可以,但不管哪種組合方式,組合中的每一個(gè)策略都會(huì)執(zhí)行

重試回退策略

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

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

NoBackOffPolicy:無(wú)退避算法策略,每次重試時(shí)立即重試

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

UniformRandomBackOffPolicy:隨機(jī)時(shí)間退避策略,需設(shè)置sleeper、minBackOffPeriod和maxBackOffPeriod,該策略在[minBackOffPeriod,maxBackOffPeriod之間取一個(gè)隨機(jī)休眠時(shí)間,minBackOffPeriod默認(rèn)500毫秒,maxBackOffPeriod默認(rèn)1500毫秒

ExponentialBackOffPolicy:指數(shù)退避策略,需設(shè)置參數(shù)sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠時(shí)間,默認(rèn)100毫秒,maxInterval指定最大休眠時(shí)間,默認(rèn)30秒,multiplier指定乘數(shù),即下一次休眠時(shí)間為當(dāng)前休眠時(shí)間*multiplier

ExponentialRandomBackOffPolicy:隨機(jī)指數(shù)退避策略,引入隨機(jī)乘數(shù)可以實(shí)現(xiàn)隨機(jī)乘數(shù)回退

guava-retrying 談話

小華:我們系統(tǒng)也要用到重試

項(xiàng)目經(jīng)理:小明前段時(shí)間用了 spring-retry,分享下應(yīng)該還不錯(cuò)

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

小華:我們項(xiàng)目中想根據(jù)對(duì)象的屬性來(lái)進(jìn)行重試。你可以看下 guava-retry,我很久以前用過(guò),感覺(jué)還不錯(cuò)。

小明:好的。

guava-retrying

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

優(yōu)勢(shì)

guava retryer工具與spring-retry類似,都是通過(guò)定義重試者角色來(lái)包裝正常邏輯重試,但是Guava retryer有更優(yōu)的策略定義,在支持重試次數(shù)和重試頻度控制基礎(chǔ)上,能夠兼容支持多個(gè)異常或者自定義實(shí)體對(duì)象的重試源定義,讓重試功能有更多的靈活性。

Guava Retryer也是線程安全的,入口調(diào)用邏輯采用的是 java.util.concurrent.Callablecall() 方法

代碼例子 入門(mén)案例

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

HelloDemo.java

public static void main(String[] args) {
    Callable callable = new Callable() {
        @Override
        public Boolean call() throws Exception {
            // do something useful here
            LOGGER.info("call...");
            throw new RuntimeException();
        }
    };

    Retryer retryer = RetryerBuilder.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
重試策略

ExponentialBackoff.java

重試次數(shù):3

重試策略:固定等待 3S

Retryer retryer = RetryerBuilder.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
guava-retrying 簡(jiǎn)介 RetryerBuilder

RetryerBuilder 是一個(gè) factory 創(chuàng)建者,可以定制設(shè)置重試源且可以支持多個(gè)重試源,可以配置重試次數(shù)或重試超時(shí)時(shí)間,以及可以配置等待時(shí)間間隔,創(chuàng)建重試者 Retryer 實(shí)例。

RetryerBuilder 的重試源支持 Exception 異常對(duì)象和自定義斷言對(duì)象,通過(guò)retryIfException 和 retryIfResult 設(shè)置,同時(shí)支持多個(gè)且能兼容

retryIfException

retryIfException,拋出 runtime 異常、checked 異常時(shí)都會(huì)重試,但是拋出 error 不會(huì)重試。

retryIfRuntimeException

retryIfRuntimeException 只會(huì)在拋 runtime 異常的時(shí)候才重試,checked 異常和error 都不重試。

retryIfExceptionOfType

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

如:  

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

當(dāng)然我們還可以在只有出現(xiàn)指定的異常的時(shí)候才重試,如: 

.retryIfExceptionOfType(IllegalStateException.class)
.retryIfExceptionOfType(NullPointerException.class)  

或者通過(guò)Predicate實(shí)現(xiàn)

.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),
Predicates.instanceOf(IllegalStateException.class))) 

retryIfResult

retryIfResult 可以指定你的 Callable 方法在返回值的時(shí)候進(jìn)行重試,如  

// 返回false重試  
.retryIfResult(Predicates.equalTo(false))   

//以_error結(jié)尾才重試  
.retryIfResult(Predicates.containsPattern("_error$"))  

RetryListener

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

每次重試之后,guava-retrying 會(huì)自動(dòng)回調(diào)我們注冊(cè)的監(jiān)聽(tīng)。

可以注冊(cè)多個(gè)RetryListener,會(huì)按照注冊(cè)順序依次調(diào)用。  

.withRetryListener(new RetryListener {      
 @Override    
   public  void onRetry(Attempt attempt) {  
               logger.error("第【{}】次調(diào)用失敗" , attempt.getAttemptNumber());  
          } 
 }
) 
主要接口
序號(hào) 接口 描述 備注
1 Attempt 一次執(zhí)行任務(wù)
2 AttemptTimeLimiter 單次任務(wù)執(zhí)行時(shí)間限制 如果單次任務(wù)執(zhí)行超時(shí),則終止執(zhí)行當(dāng)前任務(wù)
3 BlockStrategies 任務(wù)阻塞策略 通俗的講就是當(dāng)前任務(wù)執(zhí)行完,下次任務(wù)還沒(méi)開(kāi)始這段時(shí)間做什么),默認(rèn)策略為:BlockStrategies.THREAD_SLEEP_STRATEGY
4 RetryException 重試異常
5 RetryListener 自定義重試監(jiān)聽(tīng)器 可以用于異步記錄錯(cuò)誤日志
6 StopStrategy 停止重試策略
7 WaitStrategy 等待時(shí)長(zhǎng)策略 (控制時(shí)間間隔),返回結(jié)果為下次執(zhí)行時(shí)長(zhǎng)
8 Attempt 一次執(zhí)行任務(wù)
9 Attempt 一次執(zhí)行任務(wù)
StopStrategy

提供三種:

StopAfterDelayStrategy

設(shè)定一個(gè)最長(zhǎng)允許的執(zhí)行時(shí)間;比如設(shè)定最長(zhǎng)執(zhí)行10s,無(wú)論任務(wù)執(zhí)行次數(shù),只要重試的時(shí)候超出了最長(zhǎng)時(shí)間,則任務(wù)終止,并返回重試異常RetryException;

NeverStopStrategy

不停止,用于需要一直輪訓(xùn)知道返回期望結(jié)果的情況;

StopAfterAttemptStrategy

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

WaitStrategy

FixedWaitStrategy

固定等待時(shí)長(zhǎng)策略;

RandomWaitStrategy

隨機(jī)等待時(shí)長(zhǎng)策略(可以提供一個(gè)最小和最大時(shí)長(zhǎng),等待時(shí)長(zhǎng)為其區(qū)間隨機(jī)值)

IncrementingWaitStrategy

遞增等待時(shí)長(zhǎng)策略(提供一個(gè)初始值和步長(zhǎng),等待時(shí)間隨重試次數(shù)增加而增加)

ExponentialWaitStrategy

指數(shù)等待時(shí)長(zhǎng)策略;

FibonacciWaitStrategy

Fibonacci 等待時(shí)長(zhǎng)策略;

ExceptionWaitStrategy

異常時(shí)長(zhǎng)等待策略;

CompositeWaitStrategy

復(fù)合時(shí)長(zhǎng)等待策略;

總結(jié) 優(yōu)雅重試共性和原理

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

約定重試間隔,差異性重試策略,設(shè)置重試超時(shí)時(shí)間,進(jìn)一步保證重試有效性以及重試流程穩(wěn)定性。

都使用了命令設(shè)計(jì)模式,通過(guò)委托重試對(duì)象完成相應(yīng)的邏輯操作,同時(shí)內(nèi)部封裝實(shí)現(xiàn)重試邏輯。

spring-retry 和 guava-retry 工具都是線程安全的重試,能夠支持并發(fā)業(yè)務(wù)場(chǎng)景的重試邏輯正確性。

優(yōu)雅重試適用場(chǎng)景

功能邏輯中存在不穩(wěn)定依賴場(chǎng)景,需要使用重試獲取預(yù)期結(jié)果或者嘗試重新執(zhí)行邏輯不立即結(jié)束。比如遠(yuǎn)程接口訪問(wèn),數(shù)據(jù)加載訪問(wèn),數(shù)據(jù)上傳校驗(yàn)等等。

對(duì)于異常場(chǎng)景存在需要重試場(chǎng)景,同時(shí)希望把正常邏輯和重試邏輯解耦。

對(duì)于需要基于數(shù)據(jù)媒介交互,希望通過(guò)重試輪詢檢測(cè)執(zhí)行邏輯場(chǎng)景也可以考慮重試方案。

談話

項(xiàng)目經(jīng)理:我覺(jué)得 guava-retry 挺好的,就是不夠方便。小明啊,你給封裝個(gè)基于注解的吧。

小明:……

更好的實(shí)現(xiàn)

java 重試框架——sisyphus

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76667.html

相關(guān)文章

  • spring retry, guava retrying 的整合-sisyphus java 重試

    摘要:特性支持過(guò)程式編程基于字節(jié)碼的代理重試基于注解的重試,允許自定義注解無(wú)縫接入接口與注解的統(tǒng)一解決與中的不足之處設(shè)計(jì)目的綜合了和的優(yōu)勢(shì)。基于字節(jié)碼實(shí)現(xiàn)的代理重試,可以不依賴。提供基于代碼模式字節(jié)碼增強(qiáng)實(shí)現(xiàn)的方式。 Sisyphus 支持過(guò)程式編程和注解編程的 java 重試框架。 特性 支持 fluent 過(guò)程式編程 基于字節(jié)碼的代理重試 基于注解的重試,允許自定義注解 無(wú)縫接入 sp...

    宋華 評(píng)論0 收藏0
  • Spring 指南(spring-retry

    摘要:包含一些狀態(tài)來(lái)決定是重試還是中止,但是這個(gè)狀態(tài)位于堆棧上,不需要將它存儲(chǔ)在全局的任何位置,因此我們將此稱為無(wú)狀態(tài)重試。將拋出原始異常,除非在有狀態(tài)的情況下,當(dāng)沒(méi)有可用的恢復(fù),在這種情況下,它將拋出。 spring-retry 該項(xiàng)目為Spring應(yīng)用程序提供聲明式重試支持,它用于Spring Batch、Spring Integration、Apache Hadoop的Spring(以...

    xiaotianyi 評(píng)論0 收藏0
  • RestTemplate集成Ribbbon

    摘要:的類圖如下主要根據(jù)創(chuàng)建擴(kuò)展了,創(chuàng)建攔截的,這里會(huì)設(shè)置攔截器,這是集成的核心,當(dāng)發(fā)起請(qǐng)求調(diào)用的時(shí)候,會(huì)先經(jīng)過(guò)攔截器,然后才真正發(fā)起請(qǐng)求。和是配合使用的,最大重試次數(shù)是針對(duì)每一個(gè)的,如果設(shè)置,這樣觸發(fā)最大重試次數(shù)就是次。 上一篇文章我們分析了ribbon的核心原理,接下來(lái)我們來(lái)看看springcloud是如何集成ribbon的,不同的springcloud的組件(feign,zuul,Re...

    wall2flower 評(píng)論0 收藏0
  • 程序員筆記|詳解Eureka 緩存機(jī)制

    摘要:和二級(jí)緩存影響狀態(tài)更新,縮短這兩個(gè)定時(shí)任務(wù)周期可減少滯后時(shí)間,例如配置更新周期更新周期服務(wù)提供者保證服務(wù)正常下線。服務(wù)提供者延遲下線。 引言 Eureka是Netflix開(kāi)源的、用于實(shí)現(xiàn)服務(wù)注冊(cè)和發(fā)現(xiàn)的服務(wù)。Spring Cloud Eureka基于Eureka進(jìn)行二次封裝,增加了更人性化的UI,使用更為方便。但是由于Eureka本身存在較多緩存,服務(wù)狀態(tài)更新滯后,最常見(jiàn)的狀況是:服務(wù)...

    mgckid 評(píng)論0 收藏0
  • 使用 Resilience4j 框架實(shí)現(xiàn)重試機(jī)制

    摘要:重試會(huì)增加的響應(yīng)時(shí)間。提供了輔助方法來(lái)為包含遠(yuǎn)程調(diào)用的函數(shù)式接口或表達(dá)式創(chuàng)建裝飾器。如果我們想創(chuàng)建一個(gè)裝飾器并在代碼庫(kù)的不同位置重用它,我們將使用。 在本文中,我們將從快速介紹 Resilience4j 開(kāi)始,然后深入探討其 Retry 模塊。我們將了解何時(shí)、如何使用它,以及它提供的功能。在此過(guò)程中,我們還將學(xué)...

    番茄西紅柿 評(píng)論0 收藏2637

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<