Spring Batch 架構

Spring Batch 的設計著重於可擴展性以及多樣化的終端使用者群體。下圖顯示了支援終端使用者開發人員可擴展性和易用性的分層架構。

Figure 1.1: Spring Batch Layered Architecture
圖 1. Spring Batch 分層架構

此分層架構突顯了三個主要的高階組件:應用程式(Application)、核心(Core)和基礎設施(Infrastructure)。應用程式包含所有批次 Jobs 和開發人員使用 Spring Batch 撰寫的自訂程式碼。批次核心包含啟動和控制批次 Job 所需的核心執行階段類別。它包括 JobLauncherJobStep 的實作。應用程式和核心都建立在通用的基礎設施之上。此基礎設施包含通用的讀取器(readers)和寫入器(writers)以及服務(例如 RetryTemplate),這些服務既被應用程式開發人員(讀取器和寫入器,例如 ItemReaderItemWriter)使用,也被核心框架本身(重試,它本身就是一個函式庫)使用。

一般批次原則與指南

在建構批次解決方案時,應考慮以下關鍵原則、指南和一般考量。

  • 請記住,批次架構通常會影響線上架構,反之亦然。在設計時,請同時考慮這兩種架構和環境,並盡可能使用通用的建構模組。

  • 盡可能簡化,避免在單個批次應用程式中建構複雜的邏輯結構。

  • 保持資料的處理和儲存位置在物理上盡可能接近(換句話說,將您的資料保存在處理發生的地方)。

  • 盡量減少系統資源的使用,尤其是 I/O。盡可能在內部記憶體中執行更多操作。

  • 檢閱應用程式 I/O(分析 SQL 語句)以確保避免不必要的物理 I/O。特別是,需要注意以下四個常見缺陷

    • 在可以讀取一次並快取或保存在工作儲存體中的情況下,為每個交易讀取資料。

    • 在同一交易中,重新讀取先前已讀取的交易資料。

    • 導致不必要的表或索引掃描。

    • 未在 SQL 語句的 WHERE 子句中指定鍵值。

  • 不要在一個批次執行中重複執行操作。例如,如果您需要用於報表目的的資料摘要,則應(如果可能)在資料初始處理時遞增儲存的總數,以便您的報表應用程式不必重新處理相同的資料。

  • 在批次應用程式開始時分配足夠的記憶體,以避免在處理過程中耗時的重新分配。

  • 始終假設資料完整性方面最壞的情況。插入足夠的檢查和記錄驗證以維護資料完整性。

  • 在可能的情況下,實作內部驗證的檢查總和。例如,平面檔案應具有尾部記錄,指示檔案中記錄的總數以及鍵欄位的總計。

  • 在類似生產的環境中使用實際的資料量,儘早規劃和執行壓力測試。

  • 在大型批次系統中,備份可能具有挑戰性,尤其是在系統以 24-7 方式與線上應用程式同時運行的情況下。資料庫備份通常在線上設計中得到妥善處理,但應將檔案備份視為同樣重要。如果系統依賴平面檔案,則檔案備份程序不僅應到位並記錄在案,還應定期測試。

批次處理策略

為了幫助設計和實作批次系統,應以範例結構圖和程式碼外殼的形式向設計人員和程式設計師提供基本的批次應用程式建構模組和模式。在開始設計批次 Job 時,應將業務邏輯分解為一系列步驟,這些步驟可以使用以下標準建構模組來實作

  • 轉換應用程式: 對於外部系統提供或產生的每種檔案類型,必須建立轉換應用程式,以將提供的交易記錄轉換為處理所需的標準格式。此類型的批次應用程式可以部分或全部由翻譯實用程式模組組成(請參閱基本批次服務)。

  • 驗證應用程式: 驗證應用程式確保所有輸入和輸出記錄都是正確且一致的。驗證通常基於檔案標頭和尾部、檢查總和和驗證演算法以及記錄級別的交叉檢查。

  • 提取應用程式: 提取應用程式從資料庫或輸入檔案讀取一組記錄,根據預定義的規則選擇記錄,並將記錄寫入輸出檔案。

  • 提取/更新應用程式: 提取/更新應用程式從資料庫或輸入檔案讀取記錄,並根據每個輸入記錄中找到的資料,對資料庫或輸出檔案進行變更。

  • 處理和更新應用程式: 處理和更新應用程式對來自提取或驗證應用程式的輸入交易執行處理。處理通常涉及讀取資料庫以取得處理所需的資料,可能更新資料庫並建立用於輸出處理的記錄。

  • 輸出/格式應用程式: 輸出/格式應用程式讀取輸入檔案,根據標準格式重新建構此記錄中的資料,並產生用於列印或傳輸到另一個程式或系統的輸出檔案。

此外,應為無法使用先前提及的建構模組建構的業務邏輯提供基本應用程式外殼。

除了主要建構模組外,每個應用程式都可以使用一個或多個標準實用程式步驟,例如

  • 排序:一個程式,用於讀取輸入檔案並產生輸出檔案,其中記錄已根據記錄中的排序鍵欄位重新排序。排序通常由標準系統實用程式執行。

  • 分割:一個程式,用於讀取單個輸入檔案,並根據欄位值將每個記錄寫入多個輸出檔案之一。分割可以客製化,也可以由參數驅動的標準系統實用程式執行。

  • 合併:一個程式,用於從多個輸入檔案讀取記錄,並產生一個輸出檔案,其中包含來自輸入檔案的組合資料。合併可以客製化,也可以由參數驅動的標準系統實用程式執行。

批次應用程式還可以根據其輸入來源進行分類

  • 資料庫驅動的應用程式由從資料庫檢索的列或值驅動。

  • 檔案驅動的應用程式由從檔案檢索的記錄或值驅動。

  • 訊息驅動的應用程式由從訊息佇列檢索的訊息驅動。

任何批次系統的基礎都是處理策略。影響策略選擇的因素包括:估計的批次系統量、與線上系統或其他批次系統的並行性、可用的批次處理時間窗口。(請注意,隨著越來越多的企業希望 24x7 全天候運作,清晰的批次處理時間窗口正在消失)。

批次的典型處理選項是(依實作複雜性遞增排序)

  • 離線模式下在批次處理時間窗口期間的正常處理。

  • 並行批次或線上處理。

  • 同時平行處理許多不同的批次執行或 Jobs。

  • 分割(同時處理相同 Job 的多個實例)。

  • 先前選項的組合。

商業排程器可以支援部分或所有這些選項。

本節的其餘部分更詳細地討論了這些處理選項。請注意,根據經驗法則,批次處理採用的 commit 和鎖定策略取決於執行的處理類型,線上鎖定策略也應使用相同的原則。因此,在設計整體架構時,批次架構不能僅僅是事後才考慮的。

鎖定策略可以是僅使用正常的資料庫鎖定,或在架構中實作額外的自訂鎖定服務。鎖定服務將追蹤資料庫鎖定(例如,透過將必要資訊儲存在專用的資料庫表中),並授予或拒絕請求資料庫操作的應用程式程式的權限。此架構也可以實作重試邏輯,以避免在發生鎖定情況時中止批次 Job。

1. 批次處理時間窗口中的正常處理 對於在單獨的批次處理時間窗口中運行的簡單批次處理,其中線上使用者或其他批次處理不需要更新的資料,並行性不是問題,並且可以在批次執行結束時完成單個 commit。

在大多數情況下,更穩健的方法更合適。請記住,批次系統的複雜性和處理的資料量都隨著時間的推移而增長。如果沒有鎖定策略到位,並且系統仍然依賴單個 commit 點,則修改批次程式可能會很痛苦。因此,即使對於最簡單的批次系統,也要考慮 commit 邏輯對於重新啟動-恢復選項的需求,以及本節稍後描述的更複雜情況的資訊。

2. 並行批次或線上處理 可以由線上使用者同時更新的批次應用程式處理資料時,不應鎖定線上使用者可能需要的任何資料(無論是在資料庫還是在檔案中)超過幾秒鐘。此外,應在每幾個交易結束時將更新 commit 到資料庫。這樣做可以最大限度地減少其他處理程序無法使用的資料部分,以及資料無法使用的經過時間。

另一個最大限度地減少物理鎖定的選項是實作邏輯列級別鎖定,使用樂觀鎖定模式或悲觀鎖定模式。

  • 樂觀鎖定假設記錄爭用的可能性較低。它通常意味著在每個資料庫表中插入時間戳記欄位,該表由批次和線上處理同時使用。當應用程式提取要處理的列時,它也會提取時間戳記。當應用程式然後嘗試更新已處理的列時,更新會在 WHERE 子句中使用原始時間戳記。如果時間戳記匹配,則更新資料和時間戳記。如果時間戳記不匹配,則表示另一個應用程式在提取和更新嘗試之間更新了同一列。因此,無法執行更新。

  • 悲觀鎖定是任何鎖定策略,它假設記錄爭用的可能性很高,因此,需要在檢索時取得物理鎖定或邏輯鎖定。一種悲觀邏輯鎖定類型在資料庫表中使用了專用的鎖定欄位。當應用程式檢索要更新的列時,它會在鎖定欄位中設定一個標誌。在標誌到位的情況下,嘗試檢索同一列的其他應用程式在邏輯上會失敗。當設定標誌的應用程式更新列時,它也會清除標誌,使其他應用程式可以檢索該列。請注意,資料的完整性也必須在初始提取和設定標誌之間維護 — 例如,透過使用資料庫鎖定(例如 SELECT FOR UPDATE)。另請注意,此方法也存在與物理鎖定相同的缺點,只是它在管理建構逾時機制方面稍微容易一些,如果使用者在記錄鎖定時去吃午餐,該機制會釋放鎖定。

這些模式不一定適用於批次處理,但它們可能用於並行批次和線上處理(例如,在資料庫不支援列級別鎖定的情況下)。作為一般規則,樂觀鎖定更適合線上應用程式,而悲觀鎖定更適合批次應用程式。無論何時使用邏輯鎖定,都必須對所有存取受邏輯鎖定保護的資料實體的應用程式使用相同的方案。

請注意,這兩種解決方案都僅解決鎖定單個記錄的問題。通常,我們可能需要鎖定邏輯上相關的記錄群組。使用物理鎖定,您必須非常仔細地管理這些鎖定,以避免潛在的死鎖。使用邏輯鎖定,通常最好建構一個邏輯鎖定管理器,該管理器了解您要保護的邏輯記錄群組,並且可以確保鎖定是連貫且非死鎖的。此邏輯鎖定管理器通常使用自己的表來進行鎖定管理、爭用報告、逾時機制和其他問題。

3. 平行處理 平行處理讓多個批次執行或 Jobs 平行運行,以最大限度地減少總經過批次處理時間。只要 Jobs 不共享相同檔案、資料庫表或索引空間,這就不是問題。如果它們共享,則應透過使用分割資料來實作此服務。另一個選項是建構一個架構模組,透過使用控制表來維護相互依賴性。控制表應包含每個共享資源的列,以及應用程式是否正在使用它。然後,平行 Job 中的批次架構或應用程式將從該表中檢索資訊,以確定它是否可以存取它需要的資源。

如果資料存取不是問題,則可以透過使用額外的線程來平行處理來實作平行處理。在大型主機環境中,傳統上使用平行 Job 類別來確保所有處理程序都有足夠的 CPU 時間。無論如何,解決方案必須足夠穩健,以確保所有正在運行的處理程序的時程片段。

平行處理中的其他關鍵問題包括負載平衡和通用系統資源(例如檔案、資料庫緩衝池等)的可用性。另請注意,控制表本身很容易成為關鍵資源。

4. 分割 使用分割讓大型批次應用程式的多個版本同時運行。這樣做的目的是減少處理長時間批次 Jobs 所需的經過時間。可以成功分割的處理程序是那些可以分割輸入檔案或分割主要資料庫表以讓應用程式針對不同的資料集運行的處理程序。

此外,分割的處理程序必須設計為僅處理其分配的資料集。分割架構必須與資料庫設計和資料庫分割策略緊密相關。請注意,資料庫分割不一定意味著資料庫的物理分割(儘管在大多數情況下,這是明智的)。下圖說明了分割方法

Figure 1.2: Partitioned Process
圖 2. 分割處理程序

架構應足夠靈活,以允許動態配置分割數量。您應該考慮自動和使用者控制的配置。自動配置可以基於諸如輸入檔案大小和輸入記錄數之類的參數。

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 步驟之前完成所有分割?

  • 如果其中一個分割中止會發生什麼情況?