搞懂 Spring Batch
在日常開發(fā)過程中,我們或多或少都會涉及到數(shù)據(jù)報表、統(tǒng)計分析、定時任務(wù)之類的應(yīng)用場景。針對這些場景,我們可以采用 Hadoop 生態(tài)圈中的相關(guān)技術(shù)。
但是 Hadoop 是一種重量級的實(shí)現(xiàn)方案,實(shí)際應(yīng)用過程中存在入門門檻過高、學(xué)習(xí)周期過長、開發(fā)和維護(hù)困難等問題,對于某些體量并不是特別大的應(yīng)用場景而言并不建議使用。相反,我們希望找到一種輕量級實(shí)現(xiàn)方案來支持日常批處理功能,這就是今天我們要討論的話題。
圖 1 批處理需求和實(shí)現(xiàn)方案
那么,如何實(shí)現(xiàn)輕量級的批處理呢?讓我們先從相關(guān)設(shè)計理念開始講起。
輕量級批處理基本架構(gòu)
在考慮批處理架構(gòu)之前,我們站在最高的抽象度上,可以把批處理過程看作是一個流程,包括讀數(shù)據(jù)、處理數(shù)據(jù)和寫數(shù)據(jù),而這些數(shù)據(jù)背后是各種數(shù)據(jù)存儲媒介。
圖 2 批處理流程的抽象
批處理架構(gòu)的抽象過程
和普通應(yīng)用程序一樣,對于如何實(shí)現(xiàn)上述流程,我們第一個需要考慮的設(shè)計問題是如何確定所需要實(shí)現(xiàn)組件之間的職責(zé)和功能,這就需要引入分層思想。
分層結(jié)構(gòu)上,批處理架構(gòu)可以抽象為三個主要層次,基礎(chǔ)架構(gòu)層、核心處理層和應(yīng)用開發(fā)層。
基礎(chǔ)架構(gòu)層提供了通用的讀、寫、處理服務(wù),是對各種數(shù)據(jù)媒介的操作封裝;核心處理層關(guān)注于批處理的執(zhí)行過程,包括對批處理任務(wù)和流程的抽象以及如何啟動、控制這些任務(wù)與流程;應(yīng)用開發(fā)層則包含應(yīng)用程序需要實(shí)現(xiàn)的業(yè)務(wù)代碼。
圖 3 批處理技術(shù)的分層架構(gòu)
有了分層架構(gòu)之后,我們接下來對批處理的處理對象進(jìn)行建模,從而引出任務(wù)(Job)的概念。Job 就是批處理的基本對象,每個 Job 可以包含一個或多個步驟(Step),每個 Step 負(fù)責(zé)與具體的外部媒介交互,并產(chǎn)生計算結(jié)果。
我們知道,對于批處理應(yīng)用而言,處理的對象并不是一條數(shù)據(jù),而是一批數(shù)據(jù)的集合(Batch)。因此,在讀取數(shù)據(jù)階段,讀取器(Reader)可以單條執(zhí)行讀取操作,并交由數(shù)據(jù)處理器(Processor)進(jìn)行轉(zhuǎn)換或過濾處理,但在寫數(shù)據(jù)的過程中,數(shù)據(jù)寫入器(Writer)往往會以一批數(shù)據(jù)為基本操作單元。
圖 4 批處理時序圖
批處理的健壯性
在上面這個時序圖中,每一步都可能出現(xiàn)問題。因此,我們需要在出現(xiàn)問題時仍然能夠確保批處理流程執(zhí)行完畢,這就需要引入健壯性(Robustness)的概念。我們可以把批處理的健壯性簡單理解為是一種智能化機(jī)制,即在長時間不需要開發(fā)人員或業(yè)務(wù)人員干預(yù)的情況下仍然可以自動處理各種異常情況。
那么,如何實(shí)現(xiàn)健壯性呢?結(jié)合批處理的處理特性,我們可以梳理健壯性的不同實(shí)現(xiàn)策略,常見的保護(hù)三種,即忽略、重試和重啟。
圖 5 批處理健壯性的三種策略
忽略的含義在于,對于那些并不影響批處理執(zhí)行流程的異常情況,我們沒有必要停止整個任務(wù),而是可以選擇性地忽略這些異常。場景可以忽略的異常包括數(shù)字格式錯誤等。
有時候,導(dǎo)致任務(wù)執(zhí)行出現(xiàn)異常的原因并不是數(shù)據(jù)或代碼有問題,而是那些瞬態(tài)異常,常見的包括網(wǎng)絡(luò)訪問失敗或數(shù)據(jù)庫鎖等。針對這些瞬態(tài)異常,我們可以采取帶有重試次數(shù)限制的重試策略。
與前面兩種情況不同,有時候因?yàn)闃I(yè)務(wù)處理異常同樣會導(dǎo)致批處理執(zhí)行失敗。顯然這時候采用忽略或重試策略是解決不了問題的。我們需要暫停任務(wù),然后修復(fù)代碼問題之后再重新執(zhí)行任務(wù),這就是重啟策略。
在一個成熟的批處理基本架構(gòu)中,開發(fā)人員可以綜合使用這三種健壯性處理策略。而這三種策略的采用時機(jī)也是可以動態(tài)調(diào)整的,典型的例子包含:剛開始出現(xiàn)異常情況時,我們可以采用重試機(jī)制,但當(dāng)重試出現(xiàn)三次之后如果仍然拋出異常,那么我們就需要轉(zhuǎn)為采用重啟策略了。
輕量級批處理框架:Spring Batch
介紹完輕量級批處理的基本架構(gòu)之后,我們來討論它的實(shí)現(xiàn)工具。業(yè)界有許多輕量級批處理框架,今天我主要給你介紹的是,在 Spring 家族中專門針對輕量級批處理技術(shù)提供的相應(yīng)解決方案,這就是 Spring Batch。
Spring Batch 基于 Spring 和 Java,實(shí)現(xiàn)了批處理的基本架構(gòu),并支持批處理健壯性。Spring Batch 內(nèi)置包括文件、數(shù)據(jù)庫、消息中間件、外部服務(wù)在內(nèi)的多種數(shù)據(jù)讀取和寫入機(jī)制,也對數(shù)據(jù)處理過程做了轉(zhuǎn)換和過濾抽象。
針對使用場景,Spring Batch 也給提供了系統(tǒng)化的支持。使用 Spring Batch 可以應(yīng)用于定期提交批處理任務(wù)、按順序處理依賴的任務(wù)、部分處理、批處理事務(wù)支持以及消息傳遞等基礎(chǔ)設(shè)施集成等場景。
Spring Batch 的設(shè)計理念之一在于以接口形式暴露通用核心的服務(wù)并提供了完整的默認(rèn)實(shí)現(xiàn)。Spring Batch 的核心接口如下,分別對應(yīng)批處理的三個主要步驟??梢钥吹阶x和處理操作的對象是一個 Item,而寫操作則使用 Item 列表。這點(diǎn)與我們前面的分析完全一致。代碼 1。
public interface ItemReader<T> {
T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException;
}
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}
public interface ItemWriter<T> {
void write(List<? extends T> items) throws Exception;
}
其中,ItemReader 和 ItemWriter 分別實(shí)現(xiàn)數(shù)據(jù)讀取和數(shù)據(jù)寫入,對象可以包括文本文件、XML 文件、數(shù)據(jù)庫、服務(wù)和 JMS 等多種形式。
然后,ItemProcessor 代表處理器模型,Spring Batch 中的數(shù)據(jù)處理有轉(zhuǎn)換(Transformation)和過濾(Filtering)兩種主要的場景。轉(zhuǎn)換的形式有多種,基本的數(shù)據(jù)狀態(tài)和數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換比較常見。而過濾的目的是決定是否進(jìn)行 Writer 操作。無論是轉(zhuǎn)換還是過濾,Spring Batch 都為開發(fā)人員提供了擴(kuò)展接口,我們可以基于業(yè)務(wù)邏輯實(shí)現(xiàn)自定義的復(fù)雜機(jī)制。
針對批處理的健壯性,Spring Batch 也同時支持 Skip、Retry 和 Restart 這三種策略。為了實(shí)現(xiàn)這三種策略,Spring Batch 對 Job 進(jìn)行了進(jìn)一步抽象。對于任何一個 Job,運(yùn)行過程中都存在一種一對多的關(guān)系,即 Job 的定義應(yīng)該只有一份,但可以有多次執(zhí)行。因此,Spring Batch 中針對每個 Job 會生成一個 Job Instance,然后每次 Job 執(zhí)行對應(yīng)一個 Job Execution。這樣,健壯性策略在過程中會根據(jù) Job Instance 生成一個新的 Job Execution 并放在 Job Repository 中。
圖 6 Spring Batch 中的 Job Instance 和 Execution
上圖中的 Job Repository 保存批處理運(yùn)行時詳細(xì)信息,Spring Batch 支持 In-memory 和 JDBC 兩種持久化實(shí)現(xiàn)策略。
總結(jié)來說,站在最高的抽象層次上,所有批處理的過程都包括讀數(shù)據(jù)、處理數(shù)據(jù)和寫數(shù)據(jù)三大部分。雖然,普通的數(shù)據(jù)處理技術(shù)也可以實(shí)現(xiàn)這三個步驟,但一些關(guān)鍵特性使得批處理與這些數(shù)據(jù)數(shù)據(jù)技術(shù)有本質(zhì)性區(qū)別。批處理面向海量數(shù)據(jù),要求在實(shí)現(xiàn)自動化的前提下還需要保證處理過程的健壯性、可靠性和性能。Spring Batch 作為一款優(yōu)秀的批處理開源框架,為開發(fā)人員提供了輕量級批處理的一整套解決方案。
總結(jié)
我們系統(tǒng)分析了輕量級批處理技術(shù)的方方面面。我們看到作為一個常見的技術(shù)體系,想要實(shí)現(xiàn)批處理,開發(fā)人員需要考慮的維度非常多。
我們首先站在架構(gòu)設(shè)計的角度,對批處理執(zhí)行過程進(jìn)行了抽象,并給出了分層架構(gòu)。在分層架構(gòu)的基礎(chǔ)上,我們就引出了批處理的健壯性需求,并同樣闡述了三種實(shí)現(xiàn)方案?;谶@些討論的設(shè)計思想,業(yè)界也存在一些輕量級批處理框架,比如 EasyBatch。今天的內(nèi)容我們關(guān)注 Spring 家族的 Spring Batch 框架,可以看到該框架的實(shí)現(xiàn)過程和我們的設(shè)計思想是高度一致的。