Spring Batch 架構
Spring Batch 在設計時考慮了可擴充套件性和多樣化的終端使用者群。下圖展示了支援終端使用者開發者可擴充套件性和易用性的分層架構。

這個分層架構突出了三個主要的頂層元件:應用(Application)、核心(Core)和基礎設施(Infrastructure)。應用層包含所有批處理 Job 和開發者使用 Spring Batch 編寫的自定義程式碼。批處理核心層(Batch Core)包含啟動和控制批處理 Job 所需的核心執行時類。它包括 JobLauncher
、Job
和 Step
的實現。應用層和核心層都構建在一個通用的基礎設施之上。這個基礎設施包含通用的 Reader 和 Writer 以及服務(例如 RetryTemplate
),這些服務既被應用開發者使用(例如 ItemReader
和 ItemWriter
),也被核心框架本身使用(例如 Retry,它是一個獨立的庫)。
通用批處理原則和指南
在構建批處理解決方案時,應考慮以下關鍵原則、指南和一般注意事項。
-
記住,批處理架構通常會影響線上架構,反之亦然。設計時應同時考慮兩種架構和環境,儘可能使用通用構建塊。
-
儘可能簡化,避免在單個批處理應用中構建複雜的邏輯結構。
-
將資料的處理和儲存物理上保持靠近(換句話說,將資料保留在處理發生的地方)。
-
最小化系統資源使用,尤其是 I/O。儘可能在記憶體中執行更多操作。
-
審查應用 I/O(分析 SQL 語句),確保避免不必要的物理 I/O。特別需要尋找以下四個常見缺陷:
-
每次事務都讀取資料,而這些資料可以一次讀取、快取或儲存在工作儲存中。
-
在同一事務中已讀取過的資料在後續處理中再次讀取。
-
導致不必要的表或索引掃描。
-
在 SQL 語句的
WHERE
子句中未指定鍵值。
-
-
在一次批處理執行中不要重複做同樣的事情。例如,如果您需要資料彙總用於報告,您應該(如果可能)在資料首次處理時遞增儲存的總計,這樣您的報告應用就不必重新處理相同的資料。
-
在批處理應用開始時分配足夠的記憶體,以避免處理過程中耗時的重新分配。
-
對於資料完整性,始終做最壞的打算。插入足夠的檢查和記錄驗證以維護資料完整性。
-
儘可能為內部驗證實現校驗和。例如,平面檔案應包含一個尾部記錄,說明檔案中的總記錄數和關鍵欄位的聚合值。
-
儘早在類似生產環境中使用真實資料量規劃和執行壓力測試。
-
在大型批處理系統中,備份可能具有挑戰性,尤其是當系統與線上應用同時執行且是 24x7 的時候。資料庫備份通常在線上設計中得到很好的處理,但檔案備份也應視為同樣重要。如果系統依賴於平面檔案,檔案備份程式不僅應該到位和文件化,還應該定期進行測試。
批處理策略
為了幫助設計和實現批處理系統,應以示例結構圖和程式碼模板的形式向設計人員和程式設計師提供基本的批處理應用構建塊和模式。當開始設計批處理 Job 時,應將業務邏輯分解為一系列步驟,這些步驟可以使用以下標準構建塊來實現:
-
轉換應用:對於由外部系統提供或生成給外部系統的每種檔案型別,必須建立一個轉換應用,將提供的事務記錄轉換為處理所需的標準格式。這種型別的批處理應用可以部分或完全由轉換工具模組組成(參見基本批處理服務)。
-
驗證應用:驗證應用確保所有輸入和輸出記錄都是正確且一致的。驗證通常基於檔案頭和尾部、校驗和、驗證演算法以及記錄級交叉檢查。
-
提取應用:提取應用從資料庫或輸入檔案中讀取一組記錄,根據預定義的規則選擇記錄,並將記錄寫入輸出檔案。
-
提取/更新應用:提取/更新應用從資料庫或輸入檔案中讀取記錄,並根據每個輸入記錄中的資料對資料庫或輸出檔案進行更改。
-
處理和更新應用:處理和更新應用對來自提取或驗證應用的輸入事務執行處理。處理通常涉及讀取資料庫以獲取處理所需的資料,可能更新資料庫併為輸出處理建立記錄。
-
輸出/格式化應用:輸出/格式化應用讀取輸入檔案,根據標準格式重組此記錄中的資料,並生成用於列印或傳輸到其他程式或系統的輸出檔案。
此外,對於無法使用上述構建塊構建的業務邏輯,應提供一個基本的應用模板。
除了主要的構建塊外,每個應用還可以使用一個或多個標準工具步驟,例如:
-
排序:一個程式,讀取一個輸入檔案並生成一個輸出檔案,其中記錄根據記錄中的排序鍵欄位重新排序。排序通常由標準系統工具完成。
-
分割:一個程式,讀取一個輸入檔案,並根據欄位值將每條記錄寫入多個輸出檔案之一。分割可以定製,或由引數驅動的標準系統工具完成。
-
合併:一個程式,從多個輸入檔案中讀取記錄,並生成一個包含來自輸入檔案組合資料的輸出檔案。合併可以定製,或由引數驅動的標準系統工具完成。
批處理應用還可以根據其輸入源進行分類:
-
資料庫驅動的應用由從資料庫檢索的行或值驅動。
-
檔案驅動的應用由從檔案檢索的記錄或值驅動。
-
訊息驅動的應用由從訊息佇列檢索的訊息驅動。
任何批處理系統的基礎是處理策略。影響策略選擇的因素包括:估計的批處理系統容量、與線上系統或其他批處理系統的併發性、可用的批處理視窗。(注意,隨著越來越多的企業希望 24x7 執行,清晰的批處理視窗正在消失)。
批處理的典型處理選項(按實現複雜性遞增的順序)如下:
-
在離線模式下在批處理視窗期間進行正常處理。
-
併發批處理或線上處理。
-
同時並行處理許多不同的批處理執行或 Job。
-
分割槽(同時處理同一個 Job 的多個例項)。
-
上述選項的組合。
商業排程程式可能支援其中一些或所有選項。
本節的其餘部分更詳細地討論了這些處理選項。請注意,根據經驗法則,批處理過程採用的提交和鎖定策略取決於執行的處理型別,並且線上鎖定策略也應該使用相同的原則。因此,在設計整體架構時,批處理架構不能僅僅是事後才考慮的問題。
鎖定策略可以使用正常的資料庫鎖,或者在架構中實現額外的自定義鎖定服務。鎖定服務會跟蹤資料庫鎖定(例如,透過將必要資訊儲存在專門的資料庫表中),並授予或拒絕請求資料庫操作的應用程式的許可權。架構還可以實現重試邏輯,以避免在發生鎖定情況時中止批處理 Job。
1. 批處理視窗中的正常處理 對於在獨立批處理視窗中執行的簡單批處理過程,其中更新的資料不需要由線上使用者或其他批處理過程使用,併發性不是問題,並且可以在批處理執行結束時進行一次提交。
在大多數情況下,更強大的方法更合適。請記住,批處理系統傾向於隨著時間的推移而增長,無論是在複雜性還是在處理的資料量方面。如果未設定鎖定策略,並且系統仍然依賴於單一提交點,修改批處理程式可能會很痛苦。因此,即使是使用最簡單的批處理系統,也要考慮為重啟恢復選項以及本節後面描述的更復雜情況所需的資訊設定提交邏輯。
2. 併發批處理或線上處理 處理可能同時被線上使用者更新的資料的批處理應用不應鎖定任何可能被線上使用者需要的資料(無論是在資料庫還是檔案中),鎖定時間不得超過幾秒鐘。此外,應在每隔幾個事務結束時將更新提交到資料庫。這樣做可以最大程度地減少其他程序無法訪問的資料部分以及資料不可用的經過時間。
最小化物理鎖定的另一種選項是使用樂觀鎖定模式或悲觀鎖定模式來實現邏輯行級鎖定。
-
樂觀鎖定假定記錄衝突的可能性較低。它通常意味著在每個同時被批處理和線上處理使用的資料庫表中插入一個時間戳列。當應用獲取一行進行處理時,它也獲取時間戳。當應用隨後嘗試更新已處理的行時,更新在
WHERE
子句中使用原始時間戳。如果時間戳匹配,則更新資料和時間戳。如果時間戳不匹配,這表明在獲取和嘗試更新之間,另一個應用已更新了同一行。因此,更新無法執行。 -
悲觀鎖定是任何假定記錄衝突可能性高,因此需要在檢索時獲取物理或邏輯鎖定的鎖定策略。一種悲觀邏輯鎖定使用資料庫表中的專用鎖定列。當應用檢索行以進行更新時,它會在鎖定列中設定一個標誌。設定該標誌後,其他嘗試檢索同一行的應用將邏輯失敗。當設定標誌的應用更新該行時,它也會清除該標誌,從而使其他應用可以檢索該行。請注意,在初始獲取和設定標誌之間也必須保持資料完整性——例如,透過使用資料庫鎖(如
SELECT FOR UPDATE
)。另請注意,此方法與物理鎖定具有相同的缺點,但它更容易管理構建超時機制,以便在使用者鎖定記錄時去吃午飯時釋放鎖定。
這些模式不一定適用於批處理,但可能用於併發批處理和線上處理(例如在資料庫不支援行級鎖定或需要跨多個表鎖定邏輯相關記錄的情況下)。作為一般規則,樂觀鎖定更適合線上應用,而悲觀鎖定更適合批處理應用。無論何時使用邏輯鎖定,訪問受邏輯鎖定保護的資料實體的所有應用都必須使用相同的方案。
請注意,這兩種解決方案都只解決了鎖定單個記錄的問題。通常,我們可能需要鎖定邏輯相關的記錄組。使用物理鎖,您必須非常小心地管理它們以避免潛在的死鎖。使用邏輯鎖,通常最好構建一個邏輯鎖管理器,該管理器理解您希望保護的邏輯記錄組,並且可以確保鎖是一致的且不會死鎖。這個邏輯鎖管理器通常使用自己的表進行鎖管理、衝突報告、超時機制和其他關注點。
3. 並行處理 並行處理允許多個批處理執行或 Job 並行執行,以最大程度地減少總的批處理經過時間。只要 Job 不共享相同的檔案、資料庫表或索引空間,這不是問題。如果它們共享,則應使用分割槽資料來實現此服務。另一個選項是構建一個架構模組,透過使用控制表來維護相互依賴關係。控制表應包含共享資源的每一行以及它是否正被應用使用。然後,批處理架構或並行 Job 中的應用將從該表中檢索資訊,以確定它是否可以訪問所需的資源。
如果資料訪問不是問題,可以透過使用額外的執行緒來實現並行處理以並行執行。在大型機環境中,傳統上使用並行 Job 類來確保所有程序獲得足夠的 CPU 時間。無論如何,解決方案必須足夠健壯,以確保所有正在執行的程序獲得時間片。
並行處理中的其他關鍵問題包括負載均衡和通用系統資源的可用性,例如檔案、資料庫緩衝池等。此外,請注意,控制表本身很容易成為關鍵資源。
4. 分割槽 使用分割槽可以並行執行大型批處理應用的多個版本。這樣做的目的是為了減少處理長批處理 Job 所需的經過時間。可以成功分割槽的過程是那些輸入檔案可以分割或主資料庫表可以分割槽,以便應用能夠處理不同的資料集。
此外,已分割槽的過程必須設計為僅處理分配給它們的資料集。分割槽架構必須與資料庫設計和資料庫分割槽策略緊密相關。請注意,資料庫分割槽不一定意味著資料庫的物理分割槽(儘管在大多數情況下建議這樣做)。下圖說明了分割槽方法:

架構應足夠靈活,以允許動態配置分割槽數量。應考慮自動和使用者控制的配置。自動配置可以基於輸入檔案大小和輸入記錄數等引數。
4.1 分割槽方法 分割槽方法的選擇必須根據具體情況進行。以下列表描述了一些可能的分割槽方法:
1. 記錄集的固定均勻分割
這涉及將輸入記錄集分成偶數個部分(例如 10 個,其中每個部分恰好佔整個記錄集的 1/10)。然後,每個部分由批處理/提取應用的一個例項處理。
要使用此方法,需要進行預處理以分割記錄集。分割的結果是可以使用較低和較高位置邊界作為批處理/提取應用的輸入,以將其處理限制在其自己的部分。
預處理可能產生很大的開銷,因為它必須計算和確定記錄集每個部分的邊界。
2. 按鍵列分割
這涉及按鍵列(如位置程式碼)分割輸入記錄集,並將每個鍵的資料分配給一個批處理例項。為此,可以將列值:
-
透過分割槽表分配給批處理例項(本節後面描述)。
-
透過值的一部分分配給批處理例項(如 0000-0999、1000-1999 等)。
在選項 1 下,新增新值意味著手動重新配置批處理或提取,以確保新值新增到特定例項中。
在選項 2 下,這確保了所有值都被批處理 Job 的一個例項覆蓋。但是,一個例項處理的值數量取決於列值的分佈(可能在 0000-0999 範圍內有大量位置,而在 1000-1999 範圍內很少)。在此選項下,應在設計時考慮分割槽來設計資料範圍。
在這兩種選項下,無法實現記錄對批處理例項的最佳均勻分佈。無法動態配置使用的批處理例項數量。
3. 按檢視分割
此方法基本上是按鍵列分割,但在資料庫級別進行。它涉及將記錄集分割成檢視。這些檢視由批處理應用在處理期間的每個例項使用。分割是透過對資料進行分組來完成的。
使用此選項,批處理應用的每個例項都必須配置為訪問特定檢視(而不是主表)。此外,隨著新資料值的新增,必須將這組新資料包含到檢視中。由於例項數量的改變會導致檢視的改變,因此沒有動態配置能力。
4. 新增處理指示器
這涉及在輸入表中新增一個新列,該列充當指示器。作為預處理步驟,所有指示器都被標記為未處理。在批處理應用的記錄獲取階段,在單個記錄被標記為未處理的條件下讀取記錄,一旦讀取(帶有鎖),就將其標記為正在處理。當該記錄完成時,指示器會更新為完成或錯誤。您可以啟動批處理應用的許多例項而無需更改,因為附加列確保記錄只處理一次。
使用此選項,表上的 I/O 會動態增加。對於更新批處理應用,由於無論如何都會發生寫入,因此這種影響會減小。
5. 將表提取到平面檔案
此方法涉及將表提取到平面檔案中。然後可以將此檔案分割成多個段,並用作批處理例項的輸入。
使用此選項,將表提取到檔案並分割它的額外開銷可能會抵消多分割槽的效果。透過更改檔案分割指令碼可以實現動態配置。
6. 使用雜湊列
此方案涉及在用於檢索驅動記錄的資料庫表中新增一個雜湊列(鍵或索引)。此雜湊列帶有一個指示器,用於確定批處理應用的哪個例項處理該特定行。例如,如果要啟動三個批處理例項,標記“A”表示行由例項 1 處理,標記“B”表示行由例項 2 處理,標記“C”表示行由例項 3 處理。
用於檢索記錄的過程將包含一個額外的 WHERE
子句,以選擇由特定指示器標記的所有行。在此表中插入操作將涉及新增標記欄位,該欄位將預設設定為其中一個例項(例如“A”)。
一個簡單的批處理應用將用於更新指示器,例如在不同例項之間重新分配負載。當添加了足夠多的新行時,可以執行此批處理(隨時執行,批處理視窗除外)以將新行重新分配給其他例項。
額外的批處理應用例項僅需要執行批處理應用(如前幾段所述)即可重新分配指示器以適應新的例項數量。
4.2 資料庫和應用設計原則
支援針對已分割槽資料庫表執行並使用鍵列方法的多分割槽應用的架構應包含一箇中央分割槽倉庫,用於儲存分割槽引數。這提供了靈活性並確保了可維護性。該倉庫通常由一個表組成,稱為分割槽表。
分割槽表中儲存的資訊是靜態的,通常應由 DBA 維護。該表應包含多分割槽應用的每個分割槽的一行資訊。該表應包含程式 ID 程式碼、分割槽編號(分割槽的邏輯 ID)、此分割槽的資料庫鍵列的低值以及此分割槽的資料庫鍵列的高值的列。
在程式啟動時,程式 id
和分割槽編號應從架構傳遞給應用(特別是從控制處理 tasklet)。如果使用鍵列方法,這些變數用於讀取分割槽表以確定應用要處理的資料範圍。此外,在整個處理過程中必須使用分割槽編號:
-
新增到輸出檔案或資料庫更新中,以便合併過程正常工作。
-
向批處理日誌報告正常處理情況,向架構錯誤處理器報告任何錯誤。
4.3 最大程度減少死鎖
當應用並行執行或進行分割槽時,可能會發生資料庫資源爭用和死鎖。作為資料庫設計的一部分,資料庫設計團隊必須最大程度地消除潛在的爭用情況,這一點至關重要。
此外,開發人員必須確保資料庫索引表的設計考慮了死鎖預防和效能。
死鎖或熱點通常發生在管理或架構表中,如日誌表、控制表和鎖表。這些影響也應考慮在內。現實的壓力測試對於識別架構中可能的瓶頸至關重要。
為了最大程度地減少衝突對資料的影響,架構應在連線資料庫或遇到死鎖時提供服務(例如等待和重試間隔)。這意味著內建一種機制來對某些資料庫返回程式碼作出反應,並且不立即發出錯誤,而是等待預定時間並重試資料庫操作。
4.4 引數傳遞和驗證
分割槽架構應相對透明於應用開發者。架構應執行與在分割槽模式下執行應用相關的所有任務,包括:
-
在應用啟動前檢索分割槽引數。
-
在應用啟動前驗證分割槽引數。
-
在啟動時將引數傳遞給應用。
驗證應包括檢查以確保:
-
應用有足夠的分割槽來覆蓋整個資料範圍。
-
分割槽之間沒有間隔。
如果資料庫已分割槽,可能需要額外的驗證以確保單個分割槽不會跨越資料庫分割槽。
此外,架構應考慮分割槽的合併。關鍵問題包括:
-
在進入下一個 Job 步驟之前,是否必須完成所有分割槽?
-
如果其中一個分割槽中止會發生什麼?