Spring Batch 架構
Spring Batch 的設計考慮了可擴充套件性和多樣化的終端使用者群體。下圖展示了支援終端使用者開發人員的可擴充套件性和易用性的分層架構。
這種分層架構突出了三個主要的高階元件:應用、核心和基礎設施。應用包含所有批處理作業和開發人員使用 Spring Batch 編寫的自定義程式碼。批處理核心包含啟動和控制批處理作業所需的核心執行時類。它包括 JobOperator、Job 和 Step 的實現。應用和核心都建立在公共基礎設施之上。該基礎設施包含通用的讀寫器和服務(例如 RetryTemplate),這些都被應用開發人員(讀寫器,例如 ItemReader 和 ItemWriter)和核心框架本身(重試,它是一個獨立的庫)使用。
通用批處理原則和指南
在構建批處理解決方案時,應考慮以下關鍵原則、指南和一般注意事項。
-
請記住,批處理架構通常會影響線上架構,反之亦然。在設計時,要同時考慮兩種架構和環境,並儘可能使用通用的構建塊。
-
儘可能簡化,避免在單個批處理應用程式中構建複雜的邏輯結構。
-
保持資料處理和儲存在物理上彼此靠近(換句話說,將資料儲存在處理發生的地方)。
-
最小化系統資源使用,特別是 I/O。在內部記憶體中執行儘可能多的操作。
-
審查應用程式 I/O(分析 SQL 語句),以確保避免不必要的物理 I/O。特別是,需要查詢以下四個常見缺陷:
-
每次事務都讀取資料,而資料可以一次性讀取並快取或儲存在工作儲存中。
-
在同一事務中,資料已被讀取,但又為該事務重新讀取資料。
-
導致不必要的表或索引掃描。
-
在 SQL 語句的
WHERE子句中未指定鍵值。
-
-
在批處理執行中不要重複做事情。例如,如果出於報告目的需要資料彙總,您應該(如果可能)在資料初次處理時增加儲存的總計,這樣您的報告應用程式就不必重新處理相同的資料。
-
在批處理應用程式開始時分配足夠的記憶體,以避免在處理過程中耗時的重新分配。
-
始終假設資料完整性最差的情況。插入足夠的檢查和記錄驗證以維護資料完整性。
-
儘可能實施校驗和進行內部驗證。例如,平面檔案應包含一個尾記錄,說明檔案中的總記錄數和關鍵欄位的聚合值。
-
儘早在一個類似於生產的環境中使用真實資料量進行壓力測試的規劃和執行。
-
在大型批處理系統中,備份可能具有挑戰性,特別是如果系統在 24/7 的基礎上與線上應用程式併發執行。資料庫備份通常在線上設計中得到很好的處理,但檔案備份也應被視為同樣重要。如果系統依賴於平面檔案,檔案備份程式不僅應到位並有文件記錄,還應定期進行測試。
批處理策略
為了幫助設計和實現批處理系統,應以樣本結構圖和程式碼模板的形式向設計人員和程式設計師提供基本的批處理應用程式構建塊和模式。在開始設計批處理作業時,業務邏輯應分解為一系列步驟,這些步驟可以使用以下標準構建塊來實現:
-
轉換應用程式: 對於外部系統提供或生成的每種型別的檔案,都必須建立一個轉換應用程式,將提供的事務記錄轉換為處理所需的標準格式。這種型別的批處理應用程式可以部分或完全由轉換實用程式模組組成(參見基本批處理服務)。
-
驗證應用程式: 驗證應用程式確保所有輸入和輸出記錄都是正確且一致的。驗證通常基於檔案頭和尾、校驗和驗證演算法以及記錄級交叉檢查。
-
提取應用程式: 提取應用程式從資料庫或輸入檔案讀取一組記錄,根據預定義規則選擇記錄,並將記錄寫入輸出檔案。
-
提取/更新應用程式: 提取/更新應用程式從資料庫或輸入檔案讀取記錄,並根據每個輸入記錄中找到的資料對資料庫或輸出檔案進行更改。
-
處理和更新應用程式: 處理和更新應用程式對來自提取或驗證應用程式的輸入事務執行處理。處理通常涉及讀取資料庫以獲取處理所需的資料,可能更新資料庫並建立用於輸出處理的記錄。
-
輸出/格式應用程式: 輸出/格式應用程式讀取輸入檔案,根據標準格式重構此記錄中的資料,並生成用於列印或傳輸到另一個程式或系統的輸出檔案。
此外,對於無法使用前面提到的構建塊構建的業務邏輯,應提供一個基本的應用程式外殼。
除了主要的構建塊,每個應用程式還可以使用一個或多個標準實用程式步驟,例如:
-
排序:一個程式,讀取輸入檔案並生成一個輸出檔案,其中記錄已根據記錄中的排序鍵欄位重新排序。排序通常由標準系統實用程式執行。
-
拆分:一個程式,讀取單個輸入檔案並根據欄位值將每條記錄寫入多個輸出檔案之一。拆分可以定製或由引數驅動的標準系統實用程式執行。
-
合併:一個程式,從多個輸入檔案讀取記錄,並生成一個包含輸入檔案組合資料的輸出檔案。合併可以定製或由引數驅動的標準系統實用程式執行。
批處理應用程式還可以根據其輸入源進行分類:
-
資料庫驅動的應用程式由從資料庫檢索到的行或值驅動。
-
檔案驅動的應用程式由從檔案檢索到的記錄或值驅動。
-
訊息驅動的應用程式由從訊息佇列檢索到的訊息驅動。
任何批處理系統的基礎都是處理策略。影響策略選擇的因素包括:估計的批處理系統吞吐量、與線上系統或其他批處理系統的併發性、可用的批處理視窗。(請注意,隨著越來越多的企業希望 24x7 執行,清晰的批處理視窗正在消失)。
批處理的典型處理選項(按實現複雜性遞增的順序)有:
-
在批處理視窗期間以離線模式進行正常處理。
-
併發批處理或線上處理。
-
同時並行處理許多不同的批處理執行或作業。
-
分割槽(同時處理同一作業的多個例項)。
-
前述選項的組合。
其中一些或所有選項可能由商業排程器支援。
本節的其餘部分將更詳細地討論這些處理選項。請注意,根據經驗法則,批處理過程採用的提交和鎖定策略取決於所執行的處理型別,並且線上鎖定策略也應使用相同的原則。因此,在設計整體架構時,批處理架構不能僅僅是事後考慮。
鎖定策略可以是僅使用正常的資料庫鎖,也可以在架構中實現一個附加的自定義鎖定服務。該鎖定服務將跟蹤資料庫鎖定(例如,透過將必要的資訊儲存在專用的資料庫表中),並授予或拒絕請求資料庫操作的應用程式程式的許可權。此架構還可以實現重試邏輯,以避免在發生鎖定情況時中止批處理作業。
1. 批處理視窗中的正常處理 對於在單獨的批處理視窗中執行的簡單批處理過程,如果更新的資料不需要線上使用者或其他批處理過程,則併發不是問題,可以在批處理執行結束時進行一次提交。
在大多數情況下,更健壯的方法更合適。請記住,批處理系統往往會隨著時間的推移而增長,無論是複雜性還是它們處理的資料量。如果沒有鎖定策略並且系統仍然依賴於單個提交點,那麼修改批處理程式可能會很痛苦。因此,即使是最簡單的批處理系統,也要考慮提交邏輯以實現重啟恢復選項以及本節後面描述的更復雜情況的資訊的需求。
2. 併發批處理或線上處理 處理可能同時被線上使用者更新資料的批處理應用程式,不應鎖定任何線上使用者可能需要超過幾秒鐘的資料(無論是資料庫中還是檔案中)。此外,更新應在每完成幾個事務後提交到資料庫。這樣做可以最大限度地減少其他程序無法訪問的資料部分以及資料不可用的持續時間。
最小化物理鎖定的另一種選擇是使用樂觀鎖定模式或悲觀鎖定模式實現邏輯行級鎖定。
-
樂觀鎖定假設記錄爭用的可能性很低。這通常意味著在每個由批處理和線上處理同時使用的資料庫表中插入一個時間戳列。當應用程式獲取一行進行處理時,它也會獲取時間戳。當應用程式嘗試更新已處理的行時,更新操作在
WHERE子句中使用原始時間戳。如果時間戳匹配,則資料和時間戳都會更新。如果時間戳不匹配,則表示在獲取和嘗試更新之間,另一個應用程式已經更新了同一行。因此,無法執行更新。 -
悲觀鎖定是指任何假定記錄爭用可能性很高,因此需要在檢索時獲取物理或邏輯鎖定的鎖定策略。一種悲觀的邏輯鎖定型別使用資料庫表中的專用鎖定列。當應用程式檢索要更新的行時,它會在鎖定列中設定一個標誌。設定該標誌後,其他嘗試檢索同一行的應用程式將邏輯上失敗。當設定該標誌的應用程式更新該行時,它還會清除該標誌,從而允許其他應用程式檢索該行。請注意,資料完整性也必須在初始獲取和設定標誌之間保持,例如,透過使用資料庫鎖(如
SELECT FOR UPDATE)。還要注意,此方法與物理鎖定具有相同的缺點,只是構建一個超時機制更容易管理,該機制可以在使用者在記錄被鎖定時去午餐時釋放鎖定。
這些模式不一定適用於批處理,但它們可能用於併發批處理和線上處理(例如,在資料庫不支援行級鎖定的情況下)。通常,樂觀鎖定更適合線上應用程式,而悲觀鎖定更適合批處理應用程式。無論何時使用邏輯鎖定,所有訪問受邏輯鎖定保護的資料實體的應用程式都必須使用相同的方案。
請注意,這兩種解決方案都只處理單個記錄的鎖定。通常,我們可能需要鎖定一組邏輯相關的記錄。使用物理鎖,您必須非常小心地管理它們,以避免潛在的死鎖。使用邏輯鎖,通常最好構建一個邏輯鎖管理器,它能夠理解您想要保護的邏輯記錄組,並確保鎖是連貫且不會死鎖的。這個邏輯鎖管理器通常使用自己的表來管理鎖、爭用報告、超時機制和其他問題。
3. 並行處理 並行處理允許同時執行多個批處理執行或作業,以最小化總的批處理處理時間。只要這些作業不共享相同的檔案、資料庫表或索引空間,這就不成問題。如果它們共享,則應透過使用分割槽資料來實現此服務。另一個選項是構建一個架構模組,透過使用控制表來維護相互依賴關係。控制表應包含共享資源的每一行以及它是否被應用程式使用。然後,批處理架構或並行作業中的應用程式將從該表檢索資訊,以確定它是否可以訪問所需的資源。
如果資料訪問不是問題,可以透過使用額外的執行緒進行並行處理來實現並行處理。在大型機環境中,傳統上使用並行作業類來確保所有程序獲得足夠的 CPU 時間。無論如何,解決方案必須足夠健壯,以確保所有正在執行的程序的時間片。
並行處理的其他關鍵問題包括負載均衡和通用系統資源的可用性,例如檔案、資料庫緩衝區池等。此外,請注意控制表本身很容易成為關鍵資源。
4. 分割槽 使用分割槽可以併發執行大型批處理應用程式的多個版本。這樣做的目的是減少處理長時間批處理作業所需的總時間。可以成功分割槽的過程是那些輸入檔案可以拆分或主資料庫表可以分割槽以使應用程式針對不同資料集執行的過程。
此外,分割槽程序必須設計為僅處理其分配的資料集。分割槽架構必須與資料庫設計和資料庫分割槽策略緊密結合。請注意,資料庫分割槽不一定意味著資料庫的物理分割槽(儘管在大多數情況下,這是可取的)。下圖說明了分割槽方法:
架構應足夠靈活,以允許動態配置分割槽數量。您應該考慮自動和使用者控制的配置。自動配置可以基於輸入檔案大小和輸入記錄數量等引數。
4.1 分割槽方法 分割槽方法的選擇必須根據具體情況進行。以下列表描述了一些可能的分割槽方法:
1. 記錄集的固定且均勻拆分
這涉及到將輸入記錄集拆分成均勻數量的部分(例如,10 個,每個部分都精確地包含整個記錄集的 1/10)。然後,每個部分都由批處理/提取應用程式的一個例項進行處理。
要使用此方法,需要進行預處理來拆分記錄集。拆分的結果是您可以作為批處理/提取應用程式的輸入使用的下限和上限位置編號,以將其處理限制為其部分。
預處理可能會帶來很大的開銷,因為它必須計算和確定記錄集每個部分的邊界。
2. 按關鍵列拆分
這涉及到按關鍵列(例如位置程式碼)拆分輸入記錄集,並將每個關鍵列的資料分配給一個批處理例項。為此,列值可以是:
-
透過分割槽表分配給批處理例項(本節稍後描述)。
-
透過值的一部分分配給批處理例項(例如 0000-0999、1000-1999 等)。
在選項 1 下,新增新值意味著手動重新配置批處理或提取,以確保新值已新增到特定例項。
在選項 2 下,這確保了所有值都由批處理作業的一個例項覆蓋。但是,一個例項處理的值的數量取決於列值的分佈(在 0000-0999 範圍內可能有大量位置,而在 1000-1999 範圍內則很少)。在此選項下,資料範圍應在分割槽時設計。
在這兩種選項下,都無法實現記錄到批處理例項的最佳均勻分佈。沒有對所使用的批處理例項數量進行動態配置。
3. 按檢視拆分
這種方法基本上是按關鍵列拆分,但發生在資料庫層面。它涉及將記錄集拆分成檢視。這些檢視由批處理應用程式的每個例項在其處理過程中使用。拆分是透過對資料進行分組來完成的。
透過此選項,批處理應用程式的每個例項都必須配置為訪問特定檢視(而不是主表)。此外,隨著新資料值的新增,此新資料組必須包含在檢視中。沒有動態配置功能,因為例項數量的變化會導致檢視的變化。
4. 新增處理指示器
這涉及到在輸入表中新增一個新列,該列用作指示器。作為預處理步驟,所有指示器都標記為未處理。在批處理應用程式的記錄獲取階段,在單個記錄被標記為未處理的條件下讀取記錄,一旦讀取(帶鎖),它就被標記為正在處理。當該記錄完成後,指示器會更新為完成或錯誤。您可以啟動批處理應用程式的許多例項而無需更改,因為附加列確保記錄只處理一次。
使用此選項,表的 I/O 動態增加。在更新批處理應用程式的情況下,由於無論如何都必須進行寫入,因此這種影響會減小。
5. 將表提取到平面檔案
此方法涉及將表提取到平面檔案中。然後,該檔案可以拆分為多個段,並用作批處理例項的輸入。
使用此選項,將表提取到檔案並將其拆分的額外開銷可能會抵消多分割槽的影響。透過更改檔案拆分指令碼可以實現動態配置。
6. 使用雜湊列
此方案涉及向用於檢索驅動程式記錄的資料庫表新增雜湊列(鍵或索引)。此雜湊列有一個指示器,用於確定批處理應用程式的哪個例項處理此特定行。例如,如果要啟動三個批處理例項,'A' 指示器將行標記為由例項 1 處理,'B' 指示器將行標記為由例項 2 處理,'C' 指示器將行標記為由例項 3 處理。
然後,用於檢索記錄的程式將有一個附加的 WHERE 子句來選擇由特定指示器標記的所有行。此表中的插入將涉及新增標記欄位,該欄位將預設為其中一個例項(例如 'A')。
一個簡單的批處理應用程式將用於更新指示器,例如在不同例項之間重新分配負載。當添加了足夠多的新行時,可以執行此批處理(除了在批處理視窗中之外的任何時間)將新行重新分配給其他例項。
批處理應用程式的其他例項只需要執行批處理應用程式(如前所述)即可重新分配指示器以與新的例項數量一起工作。
4.2 資料庫和應用程式設計原則
支援針對分割槽資料庫表執行並使用鍵列方法的多分割槽應用程式的架構應包括一箇中央分割槽儲存庫,用於儲存分割槽引數。這提供了靈活性並確保了可維護性。儲存庫通常由一個表組成,稱為分割槽表。
分割槽表中儲存的資訊是靜態的,通常應由 DBA 維護。該表應包含多分割槽應用程式的每個分割槽的一行資訊。該表應包含程式 ID 程式碼、分割槽號(分割槽的邏輯 ID)、此分割槽的資料庫鍵列的低值和此分割槽的資料庫鍵列的高值的列。
程式啟動時,程式 id 和分割槽號應從架構(特別是從控制處理任務)傳遞給應用程式。如果使用鍵列方法,這些變數用於讀取分割槽表以確定應用程式要處理的資料範圍。此外,分割槽號必須在整個處理過程中使用,以:
-
新增到輸出檔案或資料庫更新中,以便合併過程正常工作。
-
向批處理日誌報告正常處理,向架構錯誤處理程式報告任何錯誤。
4.3 最小化死鎖
當應用程式並行執行或分割槽時,可能會發生資料庫資源爭用和死鎖。至關重要的是,資料庫設計團隊應儘可能消除潛在的爭用情況,作為資料庫設計的一部分。
此外,開發人員必須確保資料庫索引表的設計考慮了死鎖預防和效能。
死鎖或熱點經常出現在管理或架構表中,例如日誌表、控制表和鎖定表。這些問題的影響也應考慮在內。真實的壓力測試對於識別架構中可能的瓶頸至關重要。
為了最大限度地減少衝突對資料的影響,架構應在連線到資料庫或遇到死鎖時提供服務(例如等待和重試間隔)。這意味著內建機制可以對某些資料庫返回程式碼做出反應,並且不是立即發出錯誤,而是等待預定的時間量並重試資料庫操作。
4.4 引數傳遞與驗證
分割槽架構對應用程式開發人員來說應該相對透明。架構應執行與分割槽模式下執行應用程式相關的所有任務,包括:
-
在應用程式啟動前檢索分割槽引數。
-
在應用程式啟動前驗證分割槽引數。
-
在啟動時將引數傳遞給應用程式。
驗證應包括檢查以確保:
-
應用程式有足夠的分割槽來覆蓋整個資料範圍。
-
分割槽之間沒有間隙。
如果資料庫已分割槽,則可能需要進行一些額外的驗證,以確保單個分割槽不會跨越資料庫分割槽。
此外,架構還應考慮分割槽的整合。關鍵問題包括:
-
是否所有分割槽都必須在進入下一個作業步驟之前完成?
-
如果其中一個分割槽中止,會發生什麼?