配置 Spring Batch 批處理失敗重試機(jī)制

1. 引言

默認(rèn)情況下,Spring批處理作業(yè)在執(zhí)行過(guò)程中出現(xiàn)任何錯(cuò)誤都會(huì)失敗毛仪。然而有些時(shí)候哮幢,為了提高應(yīng)用程序的彈性唬党,我們就需要處理這類間歇性的故障。
在這篇短文中炬丸,我們就來(lái)一起探討 如何在Spring批處理框架中配置重試邏輯瘫寝。

2. 簡(jiǎn)單舉例

假設(shè)有一個(gè)批處理作業(yè)蜒蕾,它讀取一個(gè)CSV文件作為輸入:

username, userid, transaction_date, transaction_amount
sammy, 1234, 31/10/2015, 10000
john, 9999, 3/12/2015, 12321

然后稠炬,它通過(guò)訪問(wèn)REST端點(diǎn)來(lái)處理每條記錄,獲取用戶的 agepostCode 屬性:

public class RetryItemProcessor implements ItemProcessor<Transaction, Transaction> {
    
    @Override
    public Transaction process(Transaction transaction) throws IOException {
        log.info("RetryItemProcessor, attempting to process: {}", transaction);
        HttpResponse response = fetchMoreUserDetails(transaction.getUserId());
        //parse user's age and postCode from response and update transaction
        ...
        return transaction;
    }
    ...
}

最后咪啡,它生成并輸出一個(gè)合并的XML

<transactionRecord>
    <transactionRecord>
        <amount>10000.0</amount>
        <transactionDate>2015-10-31 00:00:00</transactionDate>
        <userId>1234</userId>
        <username>sammy</username>
        <age>10</age>
        <postCode>430222</postCode>
    </transactionRecord>
    ...
</transactionRecord>
spring batch

3. ItemProcessor 中添加重試

現(xiàn)在假設(shè)首启,如果到REST端點(diǎn)的連接由于某些網(wǎng)絡(luò)速度慢而超時(shí),該怎么辦撤摸?如果發(fā)生這種情況毅桃,則我們的批處理工作將失敗。

在這種情況下准夷,我們希望失敗的 item 處理重試幾次钥飞。因此,接下來(lái)我將批處理作業(yè)配置為:在出現(xiàn)故障時(shí)執(zhí)行最多三次重試

@Bean
public Step retryStep(
  ItemProcessor<Transaction, Transaction> processor,
  ItemWriter<Transaction> writer) throws ParseException {
    return stepBuilderFactory
      .get("retryStep")
      .<Transaction, Transaction>chunk(10)
      .reader(itemReader(inputCsv))
      .processor(processor)
      .writer(writer)
      .faultTolerant()
      .retryLimit(3)
      .retry(ConnectTimeoutException.class)
      .retry(DeadlockLoserDataAccessException.class)
      .build();
}

這里調(diào)用了 faultTolerant() 來(lái)啟用重試功能衫嵌。另外读宙,我們使用 retry 和 retryLimit 分別定義符合重試條件的異常和 item 的最大重試次數(shù)

4. 測(cè)試重試次數(shù)

假設(shè)我們有一個(gè)測(cè)試場(chǎng)景楔绞,其中返回 agepostCode 的REST端點(diǎn)關(guān)閉了一段時(shí)間结闸。在這個(gè)測(cè)試場(chǎng)景中,我們只對(duì)前兩個(gè) API 調(diào)用獲取一個(gè) ConnectTimeoutException 酒朵,而第三個(gè)調(diào)用將成功:

@Test
public void whenEndpointFailsTwicePasses3rdTime_thenSuccess() throws Exception {
    FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
    FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);

    when(httpResponse.getEntity())
      .thenReturn(new StringEntity("{ \"age\":10, \"postCode\":\"430222\" }"));
 
    //fails for first two calls and passes third time onwards
    when(httpClient.execute(any()))
      .thenThrow(new ConnectTimeoutException("Timeout count 1"))
      .thenThrow(new ConnectTimeoutException("Timeout count 2"))
      .thenReturn(httpResponse);

    JobExecution jobExecution = jobLauncherTestUtils
      .launchJob(defaultJobParameters());
    JobInstance actualJobInstance = jobExecution.getJobInstance();
    ExitStatus actualJobExitStatus = jobExecution.getExitStatus();

    assertThat(actualJobInstance.getJobName(), is("retryBatchJob"));
    assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
    AssertFile.assertFileEquals(expectedResult, actualResult);
}

在這里桦锄,我們的工作成功地完成了。另外蔫耽,從日志中可以明顯看出 第一條記錄 id=1234 失敗了兩次结耀,最后在第三次重試時(shí)成功了

19:06:57.742 [main] INFO  o.s.batch.core.job.SimpleStepHandler - Executing step: [retryStep]
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=1234
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=1234
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=1234
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=9999
19:06:57.773 [main] INFO  o.s.batch.core.step.AbstractStep - Step: [retryStep] executed in 31ms

同樣,看下另一個(gè)測(cè)試用例,當(dāng)所有重試次數(shù)都用完時(shí)會(huì)發(fā)生什么:

@Test
public void whenEndpointAlwaysFail_thenJobFails() throws Exception {
    when(httpClient.execute(any()))
      .thenThrow(new ConnectTimeoutException("Endpoint is down"));

    JobExecution jobExecution = jobLauncherTestUtils
      .launchJob(defaultJobParameters());
    JobInstance actualJobInstance = jobExecution.getJobInstance();
    ExitStatus actualJobExitStatus = jobExecution.getExitStatus();

    assertThat(actualJobInstance.getJobName(), is("retryBatchJob"));
    assertThat(actualJobExitStatus.getExitCode(), is("FAILED"));
    assertThat(actualJobExitStatus.getExitDescription(),
      containsString("org.apache.http.conn.ConnectTimeoutException"));
}

在這個(gè)測(cè)試用例中图甜,在作業(yè)因 ConnectTimeoutException 而失敗之前香伴,會(huì)嘗試對(duì)第一條記錄重試三次。

5. 使用XML配置重試

最后具则,讓我們看一下與上述配置等價(jià)的XML:

<batch:job id="retryBatchJob">
    <batch:step id="retryStep">
        <batch:tasklet>
            <batch:chunk reader="itemReader" writer="itemWriter"
              processor="retryItemProcessor" commit-interval="10"
              retry-limit="3">
                <batch:retryable-exception-classes>
                    <batch:include class="org.apache.http.conn.ConnectTimeoutException"/>
                    <batch:include class="org.springframework.dao.DeadlockLoserDataAccessException"/>
                </batch:retryable-exception-classes>
            </batch:chunk>
        </batch:tasklet>
    </batch:step>
</batch:job>

6. 簡(jiǎn)單總結(jié)

在本文中即纲,我們學(xué)習(xí)了如何在Spring批處理中配置重試邏輯,其中包括使用Java和XML配置博肋。以及使用單元測(cè)試來(lái)觀察重試在實(shí)踐中是如何工作的低斋。
如果你覺(jué)得文章還不錯(cuò),記得關(guān)注公眾號(hào): 鍋外的大佬
鍋外的大佬博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匪凡,一起剝皮案震驚了整個(gè)濱河市膊畴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌病游,老刑警劉巖唇跨,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異衬衬,居然都是意外死亡买猖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門滋尉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)玉控,“玉大人,你說(shuō)我怎么就攤上這事狮惜「吲担” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵碾篡,是天一觀的道長(zhǎng)虱而。 經(jīng)常有香客問(wèn)我,道長(zhǎng)开泽,這世上最難降的妖魔是什么牡拇? 我笑而不...
    開(kāi)封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮眼姐,結(jié)果婚禮上诅迷,老公的妹妹穿的比我還像新娘。我一直安慰自己众旗,他們只是感情好罢杉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著贡歧,像睡著了一般滩租。 火紅的嫁衣襯著肌膚如雪赋秀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天律想,我揣著相機(jī)與錄音猎莲,去河邊找鬼。 笑死技即,一個(gè)胖子當(dāng)著我的面吹牛著洼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播而叼,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼身笤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了葵陵?” 一聲冷哼從身側(cè)響起液荸,我...
    開(kāi)封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脱篙,沒(méi)想到半個(gè)月后娇钱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绊困,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年文搂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片考抄。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡细疚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出川梅,到底是詐尸還是另有隱情,我是刑警寧澤然遏,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布贫途,位于F島的核電站,受9級(jí)特大地震影響待侵,放射性物質(zhì)發(fā)生泄漏丢早。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一秧倾、第九天 我趴在偏房一處隱蔽的房頂上張望怨酝。 院中可真熱鬧,春花似錦那先、人聲如沸农猬。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)斤葱。三九已至慷垮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間揍堕,已是汗流浹背料身。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衩茸,地道東北人芹血。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像楞慈,于是被迫代替她去往敵國(guó)和親祟牲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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