物件式分散式系統的容錯,大多沿用一般分散式系統的機制與原理。但論標準化,CORBA 可說提供了最完整的規格;Java 陣營則有讓 JVM 支援主動式複製的有趣嘗試。

範例:容錯 CORBA#

CORBA 處理故障的基本手法是把物件複製成物件群組(object group)

  • 一個群組由同一物件的一或多份相同拷貝組成。
  • 群組可以像單一物件一樣被參照,提供與其成員副本相同的介面——複製對用戶端透明
  • 支援多種複製策略:主要—備援(primary-backup)複製、主動式複製、法定人數(quorum-based)複製等。

IOGR:物件群組的參考#

為了盡可能提供複製與故障透明性,物件群組不應與一般 CORBA 物件有可分辨的差異(除非應用刻意要求)。關鍵在群組如何被參照——採用一種特殊的 IOR,稱為可互通物件群組參考(Interoperable Object Group Reference, IOGR)

  • 關鍵差異:IOGR 內含指向不同物件(同群組各副本)的多個參考;一般 IOR 雖也可含多個參考,但全部指向同一個物件(只是存取協定可能不同)。
  • 用戶端把 IOGR 交給 RTS 後,RTS 嘗試繫結其中一個副本。以 IIOP 為例,RTS 可利用 profile 中的額外資訊(存放在 Components 欄位),例如以 TAG_PRIMARY/TAG_BACKUP 標籤區分主要與備援副本。
  • 若繫結某副本失敗,用戶端 RTS 可依任何合適的選擇政策繼續嘗試下一個副本。

圖 10-19:具有一個主要副本與多個備援副本的物件群組,其 IOGR 的一種可能組織方式

對用戶端而言,整個繫結程序完全透明——看起來就像在繫結一個普通的 CORBA 物件。

範例架構#

支援物件群組與故障管理需要為 CORBA 增添元件。一個可能的容錯架構衍生自 Eternal 系統,其容錯基礎設施建構在 Totem 可靠群組通訊系統之上:

  • 複製管理器(replication manager):最重要的元件,負責建立與管理複製物件群組。原則上只有一個(自身也可為容錯而複製)。
    • 用戶端建立物件群組時,只是照常呼叫 create_object 操作(由複製管理器提供)並指定物件型別,渾然不覺自己隱含建立了一個群組;初始副本數通常採系統相依的預設值。
    • 管理器也負責在故障時補齊副本,確保副本數不低於指定下限。
  • 訊息層攔截器:Eternal 攔截每次呼叫並交給獨立的複製元件,由它維護物件群組所需的一致性,並確保訊息被記錄(logging)以支援復原。
  • 呼叫接著以可靠的全序多播送往群組其他成員:
    • 主動式複製:呼叫請求交給每個副本物件的底層 RTS。
    • 被動式複製:呼叫請求只交給主要副本的 RTS,其他伺服器僅記錄請求供復原之用;主要副本完成呼叫後,把其狀態多播給備援。

圖 10-20:容錯 CORBA 系統的一個架構範例

攔截器之外還有其他做法:把容錯做進 RTS(可能影響互通性),或在 RTS 之上疊特殊服務。實務也顯示 CORBA 標準仍有未覆蓋的問題——例如在不同實作上建立副本時,沒有任何保證這樣做行得通。Felber 與 Narasimhan(2004)對 CORBA 容錯的各種做法有完整回顧與評估。

範例:容錯 Java#

鑑於 Java 的普及,也有不少工作投入為 Java 執行期系統加上容錯,一個有趣方向是讓 Java 虛擬機器(JVM)可用於主動式複製

主動式複製要求副本伺服器以**決定性有限狀態機(deterministic finite-state machine)**的方式執行(Schneider, 1990)。JVM 是絕佳候選——問題是 JVM 一點也不決定性。Napper 等人(2003)與 Friedman、Kama(2003)各自獨立指出三個非決定性的來源:

  1. 原生碼(native code):JVM 可執行透過介面掛進來的外部程式碼,對 JVM 而言是黑盒子——只看得到介面,對呼叫可能引發的(潛在非決定性)行為一無所知。要用 JVM 做主動式複製,必須確保原生碼行為決定性。
  2. 輸入資料的非決定性:例如多執行緒共同操作的共享變數,只要執行緒被允許並行,不同 JVM 實例中的值就可能不同。共享資料至少該用鎖保護——但事實證明,Java 執行期環境雖支援多執行緒,卻並不總是遵守這條規則
  3. 故障時輸出不一:發生故障時不同 JVM 會產生不同輸出,暴露機器被複製的事實,也讓 JVM 難以拉回相同狀態。若能假設所有輸出冪等(可直接重放)或可測試(能查驗輸出在崩潰前是否已產生),事情會簡化——副本伺服器需要這個假設才能決定是否重新執行某操作。

以 frame 為單位的主要—備援執行#

實務證明把 JVM 變成決定性狀態機絕非易事,還得處理副本伺服器崩潰的問題。一種可行組織是讓伺服器依主要—備援方式運行:一台伺服器協調所有動作,不時指示備援照做,兩者間需要謹慎協調。

雖然伺服器組織成主要—備援形式,這仍是主動式複製:每個副本都執行相同操作、順序相同,副本因此保持最新。主要—備援結構的用途是「以其中一台的行為為準」,確保所有伺服器表現出相同的非決定性行為。

Friedman 與 Kama(2003)的做法是讓主要伺服器先執行一個 frame 的指令:

  • 一個 frame 包含數次 context switch 的執行,結束於「所有執行緒都在等 I/O 完成」或「達到預定的 context switch 次數」。
  • 執行緒發出 I/O 操作時會被 JVM 擋下擱置;frame 開始時,主要伺服器讓所有 I/O 請求依序進行,並把結果送給其他副本——至少對 I/O 操作強制了決定性行為。
  • 明顯的問題:主要伺服器永遠超前其他副本。非主要副本崩潰時無大礙,只是容錯度下降;但主要伺服器崩潰時,可能遺失資料(更精確地說,遺失操作)。
  • 減損之道:主要伺服器以 frame 為單位工作——完成當前 frame 後才把更新資訊送給其他副本。主要伺服器處理第 k 個 frame 時,其他副本已握有處理第 k−1 個 frame 所需的全部資訊。把 frame 做小可以進一步限縮損害,代價是主要與備援之間更多的通訊。