協調一組分散式動作來作為單一作業。 如果任何一項動作失敗,可嘗試以透明的方式處理失敗,或也可以復原已執行的工作,這樣整個作業就會整體成功或失敗。 這可將復原加入分散式系統,方法是讓它復原及重試因暫時性例外狀況、持久性錯誤及程序失敗而失敗的動作。
內容和問題
應用程式會執行包含數個步驟 (其中有些可能會叫用遠端服務或存取遠端資源) 的工作。 個別的步驟可能會彼此獨立,但是它們會由實作工作的應用程式邏輯進行協調。
可能的話,應用程式應該確定工作會執行到完成,並解決存取遠端服務或資源時可能會發生的任何失敗。 失敗可能會因許多原因發生。 例如,網路可能會關閉、通訊可能會中斷、遠端服務可能沒有回應或處於不穩定的狀態,或是遠端資源可能會暫時無法存取,可能是由於資源條件約束。 在許多情況下,失敗會是暫時性的,並且可以使用重試模式來處理。
如果應用程式偵測到它無法輕鬆地復原的更永久性錯誤,就必須能夠將系統還原到一致的狀態,並確定整個作業的完整性。
解決方法
排程器代理程式監督員模式會定義下列執行者。 這些執行者會將步驟協調為以整體工作的一部分來執行。
排程器會針對組成要執行的工作步驟進行排列,並會協調其作業。 這些步驟可以結合成管線或工作流程。 排程器會負責確保此工作流程中的步驟是以正確的順序執行。 執行每個步驟時,排程器會記錄工作流程的狀態,例如「尚未啟動的步驟」、「執行中的步驟」或「步驟已完成」。狀態資訊也應該包含允許步驟完成的時間上限,稱為「依時間完成」。 如果步驟需要存取遠端服務或資源,排程器會叫用適當的代理程式,並將需執行工作的詳細資料傳遞給它。 排程器通常會使用 非同步要求/回應傳訊與代理程式通訊。 這可以使用佇列進行實作,雖然也可以改為使用其他分散式傳訊技術。
排程器會在程序管理員模式中,對程序管理員執行類似的函式。 實際工作流程通常是由排程器控制的工作流程引擎所定義及實作。 這種方法,會從排程器減少工作流程中的商務邏輯。
代理程式包含的邏輯會封裝呼叫遠端服務,或存取工作中步驟所參考的遠端資源。 每個代理程式通常會包裝對單一服務或資源的呼叫,從而實作適當的錯誤處理和重試邏輯 (受限於稍後所述的逾時條件約束)。 實作重試邏輯時,請跨所有重試嘗試傳遞穩定識別碼,讓遠端服務可以將它用於可能擁有的任何重復資料刪除邏輯。 如果排程器所執行之工作流程中的步驟跨不同步驟使用數個服務和資源,,每個步驟可能會參考不同的代理程式 (這是模式的實作詳細資料)。
監督員會監視排程器正在執行之工作中的步驟狀態。 它會定期執行, (頻率會是系統特定的) ,並檢查排程器維護的步驟狀態。 如果它偵測到任何已逾時或失敗,會針對適當的代理程式排列,以復原步驟或執行適當的補救措施 (這可能需要修改步驟的狀態)。 請注意,復原或修復動作會由排程器和代理程式實作。 監督員應該只要求執行這些動作。
排程器、代理程式和監督員是邏輯元件,它們的實體實作取決於所使用的技術。 例如,數個邏輯代理程式可能會實作為單一 Web 服務的一部分。
排程器會維護長期資料存放區中的工作進度和每個步驟的狀態 (稱為狀態存放區) 的相關資訊。 主管人員可以使用這項資訊來協助判斷步驟是否失敗。 此圖說明排程器、代理程式、主管和狀態存放區之間的關聯性。
注意
這個圖表會顯示簡化版的模式。 在實際的實作中,可能會有許多排程器的執行個體同時執行,每個都是工作的子集。 同樣地,系統可執行每個代理程式的多個執行個體,或甚至多監督員。 在此情況下,主管必須仔細協調其工作,以確保他們不會競爭以復原相同的失敗步驟和工作。 選出領導者模式會對此問題提供一個可行的解決方案。
應用程式準備好要執行工作時,它會提交要求至排程器。 排程器會在狀態存放區中記錄關於工作和其步驟的初始狀態資訊 (例如,尚未開始的步驟),然後開始執行工作流程所定義的作業。 當排程器啟動每個步驟時,會在狀態存放區中更新該步驟狀態的相關資訊 (例如,步驟執行中)。
如果步驟參考遠端服務或資源,排程器就會將訊息傳送至適當的代理程式。 除了作業的完成期限時間之外,此訊息包含代理程式傳遞至服務或存取資源時所需的資訊。 如果代理程式已成功完成其作業,就會傳回回應至排程器。 接著,排程器可以更新狀態存放區中的狀態資訊 (例如,步驟已完成) 並執行下一個步驟。 此程序會繼續,直到整個工作完成為止。
代理程式可以實作執行其工作所需的任何重試邏輯。 不過,如果代理程式未在完成期限期間結束之前完成其工作,排程器就會假設作業失敗。 在此案例中,代理程式應該停止其工作,且不嘗試將任何項目傳回排程器 (即使是錯誤訊息),或嘗試任何形式的復原。 這項限制的原因是,在步驟已逾時或失敗之後,可能會排定代理程式的另一個執行個體來執行失敗的步驟 (此程序於稍後說明)。
如果代理程式失敗,排程器就不會收到回應。 模式無法區分已逾時的步驟和真正失敗的步驟。
如果步驟逾時或失敗,狀態存放區就會包含一筆記錄,表示步驟正在執行,但將已過完成期限時間。 監督員會尋找像這樣的步驟,並嘗試加以復原。 其中一個可能的策略是讓監督員更新完整值以延長完成步驟的時間,然後將訊息傳送給排程器,以識別已逾時的步驟。然後,排程器可以嘗試重複此步驟。 不過,此設計需要工作具有 等冪性。 系統應該包含基礎結構來維護一致性。 如需詳細資訊,請參閱 可重複的基礎結構、 建構 Azure 應用程式以進行復原和可用性,以及 資源一致性決策指南。
如果監督員持續失敗或逾時,可能需要防止重試相同的步驟。若要這樣做,監督員可以在狀態存放區中維護每個步驟的重試計數,以及狀態資訊。 如果這個計數超過預先定義的臨界值,監督員可以採取策略,在通知排程器之前長時間等待,預期錯誤會在這段期間予以解決。 或者,監督員可以將訊息傳送至排程器,要求透過實作補償交易模式來復原整個工作。 這種方法會取決於提供實作每個已順利完成之步驟的補償作業所需資訊的排程器和代理程式。
監督員的目的並不是監視排程器和代理程式,並在它他們失敗時重新啟動。 系統的這個部分應該由這些元件在當中執行中的基礎結構來處理。 同樣地,監督員不應該知道排程器所執行之工作正在執行的實際商務作業 (包括如何補償這些工作應該會失敗)。 這是排程器所實作之工作流程邏輯的目的。 監督員的唯一責任是判斷步驟是否失敗,並進行排列以便其重複,或使包含失敗步驟的整個工作加以復原。
如果在失敗之後重新啟動排程器,或是排程器執行的工作流程意外終止,排程器應該能夠決定傳遞在失敗時能處理的任何工作狀態,並準備好從該點繼續這項工作。 此程式的實作詳細資料可能是系統特定的。 如果無法復原工作,可能需要復原工作已執行的工作。 這也可能需要實作補償交易。
此模式的主要優點,是在發生未預期的暫時或永久失敗時,系統會具有恢復功能。 系統可以建構為自我修復。 例如,如果代理程式或排程器失敗,可以啟動一個新的,且監督員可以排列要繼續進行的工作。 如果監督員失敗,可以啟動另一個執行個體,並可以從失敗發生的位置接手。 如果監督員排程為定期執行,就可以在預先定義的間隔之後,自動啟動新的執行個體。 可以複寫狀態存放區來觸達更高程度的復原能力。
問題和考量
當您在決定如何實作此模式時,應考慮下列幾點:
此模式可能會難以實作,且需要完整測試系統每個可能的失敗模式。
排程器所實作的復原/重試邏輯十分複雜,且取決於狀態存放區中保留的狀態資訊。 可能也必須記錄在持久性資料存放區中實作補償交易所需的資訊。
監督員的執行頻率將會相當重要。 必須執行經常來防止任何失敗的步驟長時間封鎖應用程式,但不應該太常執行以至於它變成負荷。
代理程式所執行的步驟可以執行一次以上。 實作這些步驟的邏輯應該具有等冪性。
使用此模式的時機
當分散式環境中執行的程序 (例如雲端) 必須對於通訊失敗及/或操作失敗具有彈性時,請使用此模式。
此模式可能不適用於不會叫用遠端服務或存取遠端資源的工作。
範例
實作電子商務系統的 web 應用程式已部署在 Microsoft Azure 上。 使用者可以執行此應用程式來瀏覽可用的產品以及下訂單。 使用者介面會以 web 角色執行,且應用程式的訂單處理元素會以一組背景工作角色來實作。 訂單處理邏輯有一部分牽涉到存取遠端服務,且系統的這方面可能容易發生暫時性或更多持續性的錯誤。 基於這個理由,設計工具使用了排程器代理程式監督員模式來實作系統的訂單處理元素。
當客戶下訂單時,應用程式會建構訊息來說明訂單,並將此訊息張貼至佇列。 在背景工作角色中執行的個別提交程序會擷取訊息、將訂單詳細資料插入訂單資料庫,並在狀態存放區中建立訂單處理的記錄。 請注意,插入訂單資料庫和狀態存放區會以相同作業的一部分執行。 提交程序旨在確保兩個插入一起完成。
提交處理針對訂單建立的狀態資訊包括:
OrderID。 訂單資料庫中的訂單識別碼。
LockedBy。 處理訂單的背景工作角色執行個體識別碼。 可能有多個背景工作角色的目前執行個體在執行排程器,但每筆訂單只應該由單一執行個體來處理。
CompleteBy。 訂單應該處理的時間。
ProcessState。 處理訂單的工作目前狀態。 可能的狀態為:
- 擱置: 已建立訂單,但尚未開始處理。
- 處理。 目前正在處理此訂單。
- 已處理。 已成功處理訂單。
- Error。 訂單處理失敗。
FailureCount。 已嘗試處理訂單的次數。
在這個狀態資訊,OrderID
欄位會從新的訂單的訂單識別碼複製。 LockedBy
和 CompleteBy
欄位會設定為 null
、ProcessState
欄位設定為 Pending
,而 FailureCount
欄位設為 0。
注意
在此範例中,訂單處理邏輯相當簡單,且只有叫用遠端服務的單一步驟。 在更複雜的多步驟案例中,提交程式可能會涉及數個步驟,因此會在狀態存放區中建立數筆記錄,每個記錄都會描述個別步驟的狀態。
排程器也會以背景工作角色的一部分執行,並且實作處理訂單的商務邏輯。 輪詢新訂單的排程器執行個體會檢查記錄的狀態存放區,當中的 LockedBy
欄位為 null 且 ProcessState
欄位為擱置中。 當排程器找到新的訂單時,會立即將其本身的執行個體識別碼填入 LockedBy
欄位、將 CompleteBy
欄位設定為適當的時間,並將 ProcessState
欄位設定為處理中。 程式碼是設計為專有且不可部分完成,以確保排程器的兩個並行執行個體無法嘗試同時處理相同的訂單。
然後,排程器會執行商務工作流程,以非同步方式處理訂單,從狀態存放區中,將 OrderID
欄位中的值傳遞給它。 處理訂單的工作流程會從訂單資料庫中擷取訂單的詳細資訊,並執行其工作。 當訂單處理工作流程中的步驟需要叫用遠端服務時,會使用代理程式。 工作流程步驟會使用一組 Azure 服務匯流排訊息佇列作為要求/回應通道來與代理程式通訊。 此圖顯示解決方案的高階檢視。
從工作流程步驟傳送至代理程式的訊息會說明訂單,並包括完成期限時間。 如果代理程式在完成期限時間過期之前收到來自遠端服務的回應,它會在工作流程所接聽的服務匯流排佇列上張貼回覆訊息。 當工作流程步驟收到有效的回復訊息時,它會完成其處理,而排程器會將訂單狀態的欄位設定 ProcessState
為已處理。 此時,訂單處理已順利完成。
如果完成期限時間在代理程式從遠端服務收到回應之前就過期,代理程式只會中止其處理,並終止處理訂單。 同樣地,如果處理訂單的工作流程超過完成期限時間,它也會終止。 在這兩種情況下,狀態存放區中的訂單狀態會維持設定為處理中,但是完成期限時間會表示處理訂單所經過的時間,且程序會被視為已失敗。 請注意,如果正在存取遠端服務的代理程式或正在處理訂單 (或兩者) 的工作流程非預期地終止,狀態存放區中的資訊就會再次保持設定為處理中,而且最終會出現過期的完成期限值。
如果代理程式在嘗試連絡遠端服務時偵測到無法復原、非暫時性的錯誤,可以將錯誤回應傳送回工作流程。 排程器可以將訂單狀態設定為錯誤,並引發警示操作員的事件。 接著,作業員可以嘗試以手動方式解決失敗的原因,然後再重新送出失敗的處理步驟。
監督員會定期檢查狀態存放區,尋找完成期限值過期的訂單。 如果監督員找到記錄,就會自動遞增 FailureCount
欄位。 如果失敗計數值低於指定的臨界值,監督員會將 LockedBy
欄位重設為 null、使用新的到期時間更新 CompleteBy
設定,並將 ProcessState
欄位設為擱置中。 排程器的執行個體可挑出此訂單,並照舊執行其處理。 如果失敗計數值超過指定的臨界值,就會假設失敗的原因是非暫時性的。 監督員會將訂單狀態設定為錯誤,並引發警示操作員的事件。
在此範例中,會以個別的背景工作角色來實作監督員。 您可以使用各種不同的策略來排列要執行的監督員工作,包括使用 Azure 排程器服務 (不會與此模式中的排程器元件混淆)。 如需有關 Azure 排程器服務的詳細資訊,請瀏覽排程器頁面。
雖然此範例中未顯示,但排程器可能需要保留提交通知訂單進度和狀態之訂單的應用程式。 應用程式和排程器會互相隔離,來排除它們之間的任何相依性。 應用程式不了解排程器的哪個執行個體正在處理訂單,而排程器不知道哪一個特定的應用程式執行個體會張貼訂單。
若要允許報告訂單狀態,應用程式可以使用自己的私人回應佇列。 會包含此回應佇列的詳細資料作為傳送至提交程序之要求的一部分,從而在狀態存放區中包含這項資訊。 然後,排程器會將訊息張貼到此佇列,指出訂單的狀態 (已接收要求、已完成訂單、訂單失敗等等)。 它應該包含這些訊息中的訂單識別碼,讓它們可以與應用程式的原始要求相互關聯。
下一步
實作此模式時,下列指導方針可能也相關:
非同步傳訊入門。 排程器代理程式監督員模式中的元件通常會以互相減少的方式執行,並以非同步方式進行通訊。 描述一些可用來實作以訊息佇列作為基礎的非同步通訊方法。
參考 6:Sagas 的冒險故事。 顯示 CQRS 模式如何使用程序管理員 (CQRS Journey 指南的一部分) 的範例。
相關資源
實作此模式時,下列模式可能也相關:
重試模式。 代理程式可以使用此模式明確地重試作業,存取先前已失敗的遠端服務或資源。 當您預期失敗的原因是暫時性且可以更正時,就請使用。
斷路器模式。 在連線到遠端服務或資源時,代理程式可以使用此模式來處理需要不同時間進行修正的錯誤。
補償交易模式。 如果排程器正在執行的工作流程無法順利完成,可能必須復原先前執行的任何工作。 補償交易模式會說明遵循最終一致性模型的作業如何達成這項工作。 這些作業的類型通常是由執行複雜商務程序和工作流程的排程器進行實作。
領導者選擇模式。 可能必須協調監督員多個執行個體的動作,防止它們嘗試復原相同的失敗程序。 選出領導者模式描述如何執行這項操作。
雲端架構:Clemens Vasters 部落格上的Scheduler-Agent-Supervisor 模式