guava-retrying 初體驗(yàn)

一棉磨、前言

由于最近項(xiàng)目中用到了guava-retrying江掩,并且以前沒有遇到過這個(gè)工具包,所以準(zhǔn)備通過本篇文章來系統(tǒng)的梳理下該工具包的使用。

二环形、背景

??一般在各種業(yè)務(wù)場景中策泣,為了保持系統(tǒng)穩(wěn)定,我們都會(huì)有相應(yīng)的重試機(jī)制斟赚,因?yàn)楸热缯f着降,某個(gè)接口某個(gè)數(shù)據(jù)庫鏈接由于網(wǎng)絡(luò)抖動(dòng)或者其他因素導(dǎo)致響應(yīng)失敗差油,這時(shí)候直接判定失敗或者M(jìn)ock數(shù)據(jù)未必是一種優(yōu)雅的方式拗军,因?yàn)檫@種情況下未必是接口掛掉了或者數(shù)據(jù)庫連不上了,有可能是網(wǎng)絡(luò)一時(shí)的抖動(dòng)導(dǎo)致的蓄喇,所以這時(shí)候一個(gè)優(yōu)雅的重試機(jī)制或許能幫上我們发侵。

三、實(shí)現(xiàn)

guava-retrying是Google Guava庫的一個(gè)擴(kuò)展包妆偏,可以為任意函數(shù)調(diào)用創(chuàng)建可配置的重試機(jī)制刃鳄。該擴(kuò)展包比較簡單,大約包含了10個(gè)方法和類:

github地址:https://github.com/rholder/guava-retrying

不過可以看到github上該項(xiàng)目已經(jīng)好多年沒有維護(hù)了钱骂,但這并不影響它的使用叔锐,因?yàn)樗呀?jīng)足夠穩(wěn)定了。接下來见秽,我們直接來根據(jù)個(gè)例子來學(xué)習(xí)愉烙。

首先,看下Maven配置:

<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
</dependency>

因?yàn)間uava-retrying是基于Google的核心類庫guava的重試機(jī)制實(shí)現(xiàn)解取,所以需要依賴guava的包步责,這里記得引入下。例子如下:

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
        .retryIfException()
        .retryIfResult(aBoolean -> Objects.equals(aBoolean, false))
        .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(10, TimeUnit.SECONDS, Executors.newCachedThreadPool()))
        .withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
        .withStopStrategy(StopStrategies.stopAfterAttempt(5))
        .withRetryListener(new RetryListener() {
            @Override
            public <V> void onRetry(Attempt<V> attempt) {
                System.out.print("retry time=" + attempt.getAttemptNumber());
            }
        }).build();
try {
    retryer.call(() -> {
        // 邏輯處理
        return null;
    });
} catch (Exception e) {
    System.out.println("exception:" + e);
}

這就是一個(gè)重試機(jī)制的實(shí)現(xiàn)了禀苦,比較簡單蔓肯,我們來看下具體接口及相應(yīng)的策咯。

  • newBuilder:創(chuàng)建RetryerBuilder對(duì)象振乏,通過該類進(jìn)行構(gòu)建各種重試策咯蔗包;
  • retryIfException:拋出異常時(shí)重試,但拋出error不會(huì)重試慧邮;另外該方法還包含一個(gè)重載的方法调限,可以自定義針對(duì)異常的實(shí)現(xiàn);
  • retryIfRuntimeException:見名知義赋咽,拋出RuntimeException時(shí)重試旧噪;
  • retryIfExceptionOfType:拋出指定異常類型時(shí)重試;
  • retryIfResult:根據(jù)具體的返回值選擇重試脓匿;
  • withRetryListener:在重試的時(shí)候進(jìn)行事件監(jiān)聽淘钟,這中間我們可以記錄下錯(cuò)誤日志什么的;可以注冊(cè)多個(gè)事件監(jiān)聽器陪毡,會(huì)按照注冊(cè)順序依次調(diào)用米母;
  • withWaitStrategy:重試等待策略勾扭,核心策咯之一;
  • withStopStrategy:重試停止策略铁瞒,核心策咯之一妙色;
  • withBlockStrategy:重試阻塞策略,也就是兩次重試的時(shí)間間隔的實(shí)現(xiàn)方式慧耍;
  • withAttemptTimeLimiter:單次任務(wù)執(zhí)行時(shí)長限制(如果單次任務(wù)執(zhí)行超時(shí)身辨,則終止執(zhí)行當(dāng)前任務(wù))(該方法因SimpleTimeLimiter構(gòu)造函數(shù)變更已失效無法使用);
  • build:通過newBuilder構(gòu)建了各種重試策咯芍碧,構(gòu)建完成煌珊,還需要通過build方法借助Retryer來執(zhí)行;

接下來泌豆,我們來看一下主要的幾個(gè)策咯及核心類定庵。

1. Attemp

Attemp既是一次任務(wù)重試(call),也是一次請(qǐng)求的結(jié)果踪危,記錄了當(dāng)前請(qǐng)求的重試次數(shù)蔬浙,是否包含異常和請(qǐng)求的返回值。我們可以配合監(jiān)聽器使用贞远,用于記錄重試過程的細(xì)節(jié)畴博,常用的方法有如下幾個(gè):

  • getAttemptNumber(),表示準(zhǔn)備開始第幾次重試兴革;
  • getDelaySinceFirstAttempt()绎晃,表示距離第一次重試的延遲,也就是與第一次重試的時(shí)間差杂曲,單位毫秒庶艾;
  • hasException(),表示是異常導(dǎo)致的重試還是正常返回擎勘;
  • hasResult()咱揍,表示是否返回了結(jié)果;因?yàn)橛袝r(shí)候是因?yàn)榉祷亓颂囟ńY(jié)果才進(jìn)行重試棚饵;
  • getExceptionCause()煤裙,如果是異常導(dǎo)致的重試,那么獲取具體具體的異常類型噪漾;
  • getResult()硼砰,返回重試的結(jié)果;
  • get()欣硼,如果有的話题翰,返回重試的結(jié)果;和getResult不同的在于對(duì)異常的處理;
2. Retryer

Retryer是最核心的類豹障,是用于執(zhí)行重試策咯的類冯事,通過RetryerBuilder類進(jìn)行構(gòu)造,并且RetryerBuilder負(fù)責(zé)將設(shè)置好的重試策咯添加到Retryer中血公,最終通過執(zhí)行Retryer的核心方法call來執(zhí)行重試策咯:

public V call(Callable<V> callable) throws ExecutionException, RetryException {
    long startTime = System.nanoTime();
    // 重試次數(shù)
    for (int attemptNumber = 1; ; attemptNumber++) {
        Attempt<V> attempt;
        try {
            // attemptTimeLimiter會(huì)設(shè)置業(yè)務(wù)執(zhí)行的時(shí)長限制
            V result = attemptTimeLimiter.call(callable);
            // 根據(jù)上次執(zhí)行結(jié)果構(gòu)建新的重試對(duì)象
            attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
        } catch (Throwable t) {
            // 如果有異常昵仅,構(gòu)建新的異常重試對(duì)象
            attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
        }

        // 循環(huán)遍歷監(jiān)聽器
        for (RetryListener listener : listeners) {
            listener.onRetry(attempt);
        }

        // 判斷是否滿足重試條件,來決定是否繼續(xù)等待并進(jìn)行重試
        if (!rejectionPredicate.apply(attempt)) {
            return attempt.get();
        }
        // 達(dá)到停止重試策略累魔,但還沒有結(jié)果摔笤,拋出異常
        if (stopStrategy.shouldStop(attempt)) {
            throw new RetryException(attemptNumber, attempt);
        } else {
            // 獲取等待策略中設(shè)置的重試的時(shí)長
            long sleepTime = waitStrategy.computeSleepTime(attempt);
            try {
                // 阻塞策略進(jìn)行阻塞
                blockStrategy.block(sleepTime);
            } catch (InterruptedException e) {
                // 線程中斷,拋出異常
                Thread.currentThread().interrupt();
                throw new RetryException(attemptNumber, attempt);
            }
        }
    }
}
3. WaitStrategies 重試等待策略
3.1 ExponentialWaitStrategy 指數(shù)等待策略

指數(shù)補(bǔ)償 算法 Exponential Backoff

.withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES))

創(chuàng)建一個(gè)永久重試的重試器薛夜,每次重試失敗時(shí)以遞增的指數(shù)時(shí)間等待籍茧,直到最多5分鐘版述。 5分鐘后梯澜,每隔5分鐘重試一次。對(duì)該例而言:

第一次失敗后渴析,依次等待時(shí)長:2^1 * 100;2^2 * 100晚伙;2^3 * 100;...

在ExponentialWaitStrategy中,根據(jù)重試次數(shù)計(jì)算等待時(shí)長的源碼我們可以關(guān)注下:

@Override
public long computeSleepTime(Attempt failedAttempt) {
    double exp = Math.pow(2, failedAttempt.getAttemptNumber());
    long result = Math.round(multiplier * exp);
    if (result > maximumWait) {
        result = maximumWait;
    }
    return result >= 0L ? result : 0L;
}

如果以后有類似的需求俭茧,我們可以自己寫下這些算法咆疗,而有關(guān)更多指數(shù)補(bǔ)償 算法 Exponential Backoff,可以參考:http://en.wikipedia.org/wiki/Exponential_backoff


3.2 FibonacciWaitStrategy 斐波那契等待策略

Fibonacci Backoff 斐波那契補(bǔ)償算法

.withWaitStrategy(WaitStrategies.fibonacciWait(100, 2, TimeUnit.MINUTES))

創(chuàng)建一個(gè)永久重試的重試器母债,每次重試失敗時(shí)以斐波那契數(shù)列來計(jì)算等待時(shí)間午磁,直到最多2分鐘;2分鐘后毡们,每隔2分鐘重試一次迅皇;對(duì)該例而言:

第一次失敗后,依次等待時(shí)長:1*100;1*100衙熔;2*100登颓;3*100;5*100红氯;...

3.3 FixedWaitStrategy 固定時(shí)長等待策略
withWaitStrategy(WaitStrategies.fixedWait(10,  TimeUnit.SECONDS))

固定時(shí)長等待策略框咙,失敗后,將等待固定的時(shí)長進(jìn)行重試痢甘;


3.4 RandomWaitStrategy 隨機(jī)時(shí)長等待策略
withWaitStrategy(WaitStrategies.randomWait(10,  TimeUnit.SECONDS));
withWaitStrategy(WaitStrategies.randomWait(1,  TimeUnit.SECONDS, 10, TimeUnit.SECONDS));

隨機(jī)時(shí)長等待策略喇嘱,可以設(shè)置一個(gè)隨機(jī)等待的最大時(shí)長,也可以設(shè)置一個(gè)隨機(jī)等待的時(shí)長區(qū)間塞栅。


3.5 IncrementingWaitStrategy 遞增等待策略
withWaitStrategy(WaitStrategies.incrementingWait(1,  TimeUnit.SECONDS, 5, TimeUnit.SECONDS))

遞增等待策略者铜,根據(jù)初始值和遞增值,等待時(shí)長依次遞增。就本例而言:

第一次失敗后王暗,將依次等待1s悔据;6s(1+5);11(1+5+5)s俗壹;16(1+5+5+5)s科汗;...


3.6 ExceptionWaitStrategy 異常等待策略
withWaitStrategy(WaitStrategies.exceptionWait(ArithmeticException.class, e -> 1000L))

根據(jù)所發(fā)生的異常指定重試的等待時(shí)長;如果異常不匹配绷雏,則等待時(shí)長為0头滔;


3.7 CompositeWaitStrategy 復(fù)合等待策略
.withWaitStrategy(WaitStrategies.join(WaitStrategies.exceptionWait(ArithmeticException.class, e -> 1000L),WaitStrategies.fixedWait(5, TimeUnit.SECONDS)))

復(fù)合等待策略;如果所執(zhí)行的程序滿足一個(gè)或多個(gè)等待策略涎显,那么等待時(shí)間為所有等待策略時(shí)間的總和坤检。

4. StopStrategies 重試停止策略
4.1 NeverStopStrategy
withStopStrategy(StopStrategies.neverStop())

一直不停止,一直需要重試期吓。


4.2 StopAfterAttemptStrategy
withStopStrategy(StopStrategies.stopAfterAttempt(3))

在重試次數(shù)達(dá)到最大次數(shù)之后早歇,終止任務(wù)。


4.3 StopAfterDelayStrategy
withStopStrategy(StopStrategies.stopAfterDelay(3, TimeUnit.MINUTES))

在重試任務(wù)達(dá)到設(shè)置的最長時(shí)長之后讨勤,無論任務(wù)執(zhí)行次數(shù)箭跳,都終止任務(wù)。

5. BlockStrategies 阻塞策略

阻塞策略默認(rèn)提供的只有一種:ThreadSleepStrategy潭千,實(shí)現(xiàn)方式是通過Thread.sleep(sleepTime)來實(shí)現(xiàn)谱姓;不過這也給了我們極大的發(fā)揮空間,我們可以自己實(shí)現(xiàn)阻塞策略刨晴。

6. AttemptTimeLimiters 任務(wù)執(zhí)行時(shí)長限制

這個(gè)表示單次任務(wù)執(zhí)行時(shí)間限制(如果單次任務(wù)執(zhí)行超時(shí)屉来,則終止執(zhí)行當(dāng)前任務(wù));

6.1 NoAttemptTimeLimit 無時(shí)長限制
.withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit())

顧名思義狈癞,不限制執(zhí)行時(shí)長茄靠;每次都是等執(zhí)行任務(wù)執(zhí)行完成之后,才進(jìn)行后續(xù)的重試策咯亿驾。


6.2 FixedAttemptTimeLimit
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(10, TimeUnit.SECONDS));
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(10, TimeUnit.SECONDS, Executors.newCachedThreadPool()));

可以指定任務(wù)的執(zhí)行時(shí)長限制嘹黔,并且為了控制線程管理,最好指定相應(yīng)的線程池莫瞬。

四儡蔓、總結(jié)

  1. guava-retrying功能強(qiáng)大,基本能滿足我們常用的操作疼邀;如果不滿足當(dāng)前各種已有的策咯喂江,可以選擇分別繼承WaitStrategyStopStrategy旁振,BlockStrategy來自定義自己的實(shí)現(xiàn)获询;
  2. guava-retrying默認(rèn)的阻塞策咯是通過Thread.sleep來實(shí)現(xiàn)的涨岁,也就是說通過讓當(dāng)前線程休眠來實(shí)現(xiàn)阻塞功能,這或許不是一種很好的選擇吉嚣;
  3. 在實(shí)際使用過程種梢薪,我們可能會(huì)經(jīng)常要調(diào)整重試次數(shù)、重試時(shí)間等策咯尝哆,所以我們可以將重試策咯的配置進(jìn)行參數(shù)化保存秉撇,達(dá)到動(dòng)態(tài)調(diào)節(jié)的目的;另外在使用的時(shí)候秋泄,也可以封裝成util工具類供大家使用琐馆;

本文參考:
重試?yán)髦瓽uava Retrying
https://github.com/rholder/guava-retrying

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恒序,隨后出現(xiàn)的幾起案子瘦麸,更是在濱河造成了極大的恐慌,老刑警劉巖歧胁,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滋饲,死亡現(xiàn)場離奇詭異,居然都是意外死亡与帆,警方通過查閱死者的電腦和手機(jī)了赌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玄糟,“玉大人,你說我怎么就攤上這事袄秩≌篝幔” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵之剧,是天一觀的道長郭卫。 經(jīng)常有香客問我,道長背稼,這世上最難降的妖魔是什么贰军? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蟹肘,結(jié)果婚禮上词疼,老公的妹妹穿的比我還像新娘。我一直安慰自己帘腹,他們只是感情好贰盗,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阳欲,像睡著了一般舵盈。 火紅的嫁衣襯著肌膚如雪陋率。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天秽晚,我揣著相機(jī)與錄音瓦糟,去河邊找鬼。 笑死赴蝇,一個(gè)胖子當(dāng)著我的面吹牛狸页,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扯再,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼芍耘,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了熄阻?” 一聲冷哼從身側(cè)響起斋竞,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秃殉,沒想到半個(gè)月后坝初,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钾军,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鳄袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吏恭。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拗小,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出樱哼,到底是詐尸還是另有隱情哀九,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布搅幅,位于F島的核電站阅束,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茄唐。R本人自食惡果不足惜息裸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沪编。 院中可真熱鬧呼盆,春花似錦、人聲如沸漾抬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纳令。三九已至挽荠,卻和暖如春克胳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背圈匆。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工漠另, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跃赚。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓笆搓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纬傲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子满败,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容