多數物件式分散式系統對複製物件採取傳統路線:把物件當成「附帶專屬操作的資料容器」。因此 Java beans 或 CORBA 相容系統的複製處理,大體不脫一般分散式系統的一致性與複製原理。本節聚焦於在物件式系統中特別突出的兩個主題:入口一致性與複製呼叫。

入口一致性#

物件的資料中心一致性,自然落在入口一致性(entry consistency):用同步變數(如鎖)把共享資料上的操作分組。物件本來就把資料與操作綁在一起,呼叫期間鎖住物件即可序列化存取、維持一致。

「一個物件配一把鎖」概念上簡單,但物件被複製之後,要實作入口一致性得解決兩個問題:

  1. 防止同一物件的多個呼叫並行執行:任一方法執行期間,其他方法不得執行,確保物件內部資料的存取確實被序列化。本地鎖機制即可做到。
  2. 確保所有副本的狀態變更一致:不能有兩個獨立的方法呼叫同時發生在不同副本上——必須讓每個副本以相同順序看到所有呼叫。做法有二:
    • 主要式(primary-based)方案。
    • 對所有副本做全序多播(totally-ordered multicast)

實務上常見的開發流程是:先設計單一物件(用本地鎖保護並行存取),之後再複製它。若採 primary-based 方案,應用開發者得額外花力氣序列化物件呼叫;因此假設底層 middleware 支援全序多播通常更方便——用戶端不用改、開發者不用多寫程式。至於 middleware 怎麼實現全序多播應該是透明的:底下可以是 primary-based,也可以基於 Lamport 時鐘。

執行緒排程的粒度問題#

即使 middleware 提供了全序多播,仍不足以保證物件呼叫有序。問題出在粒度:所有副本伺服器以相同順序收到呼叫請求,不代表伺服器裡的執行緒以正確順序處理它們。

  • 多執行緒物件伺服器把進來的請求丟給可用的執行緒,接著由執行緒排程器分配 CPU。
  • 若排程器不以決定性(deterministic)方式運作,同一物件上的方法呼叫順序就可能在不同副本間錯亂——處理同一筆複製呼叫的執行緒,必須在各副本上都排在處理下一筆的執行緒之前。
  • 其實不需要讓所有執行緒都決定性排程:既然請求投遞已是全序,只要保證同一個複製物件的請求依投遞順序處理即可,不同物件的呼叫仍可並行。可惜支援這種並行的系統很少。

圖 10-15:複製物件需要決定性的執行緒排程——中介軟體把無序的請求整理成全序後,各副本上的執行緒必須以相同順序被排程。

延伸研究:決定性執行緒排程的實作
  • Basile 等人(2002)的方案:確保共享同一把(本地)鎖的執行緒在每個副本上以相同順序被排程。核心是 primary-based——由其中一台副本伺服器為特定的鎖決定哪條執行緒先走;2003 年的改良版避免了伺服器間的頻繁通訊。不共享鎖的執行緒在各伺服器上仍可並行。
  • 此方案的缺點:運作在底層作業系統層級,每一把鎖都得納管。Taiani 等人(2005)指出,若提供應用層資訊、只挑出「序列化複製物件存取真正需要的鎖」,效能可大幅提升。

複製框架#

物件技術的天性帶來一個有趣優勢:功能設計額外功能面(如複製)往往可以乾淨分離,強力的實現機制是攔截器(interceptor)

Babaoglu 等人(2004)描述了一個用攔截器為 J2EE 伺服器複製 Java beans 的框架,在三個點攔截物件呼叫:

  1. 用戶端、呼叫交給 stub 之前:用於呼叫端本身也被複製的情況——此時可能得先與其他呼叫端同步,因為面對的是複製呼叫(見下節)。
  2. 用戶端 stub 內部:攔截即複製演算法的一部分;決定把請求轉發到哪裡,或在某副本連不上時實作容錯移轉(fail-over)。
  3. 伺服器端、物件即將被呼叫之前:實際上拆成兩點——請求剛進來、交給 adapter 之前,複製演算法取得控制權,分析請求對象並視需要活化所需的複製物件;呼叫前一刻,複製演算法可以讀寫複製物件的屬性值。

圖 10-16:將複製演算法與物件分離的一般性框架

這個框架可以獨立於任何複製演算法架設,達成物件功能與物件複製的完全分離

複製呼叫#

另一個必須解決的問題是複製呼叫(replicated invocation):物件 A 呼叫物件 B,B 又呼叫物件 C。若 B 被複製,B 的每個副本原則上都會獨立呼叫 C——C 被呼叫了多次而非一次。若被呼叫的方法是「轉帳十萬美元」,遲早有人要抗議。

圖 10-17:複製式方法呼叫的問題

通用解法不多:

  • 直接禁止(Maassen 等人,2001):當效能是首要考量時合理。
  • 複製感知通訊層(Mazouni 等人,1995):為容錯而複製時可採用,且與複製政策(副本如何保持一致的細節)無關。讓(複製的)物件跑在一層複製感知的通訊層之上:
    • 去程:複製物件 B 呼叫複製物件 C 時,B 的每個副本先為呼叫請求指派同一個唯一識別碼;由 B 的副本中的一個**協調者(coordinator)**把請求轉發給 C 的所有副本,其餘 B 副本把自己那份請求扣住不送。結果是 C 的每個副本只收到一份請求。
    • 回程:同樣機制反向套用——C 的協調者發現這是各副本都會產生的複製回覆,只有它把回覆轉發給 B 的各副本,其餘 C 副本扣住自己那份。
    • B 的副本收到回覆(不論當初是自己轉發還是扣住請求),就把回覆交給實際物件。

圖 10-18:(a) 把呼叫請求從一個複製物件轉發給另一個複製物件;(b) 把回覆傳回給複製物件

這個方案本質上是多播通訊,重點在防止同一訊息被不同副本重複多播,屬於傳送端方案。另一條路是接收端方案:讓接收副本偵測屬於同一次呼叫的多份訊息,只把一份交給其物件。