決定了軟體元件、元件間的互動與擺放位置,就得到軟體架構的一個實例,稱為系統架構(system architecture)。本節依序討論集中式、去中心化與各種混合式組織。

集中式架構#

儘管分散式系統許多議題缺乏共識,但有一點研究者與實務者普遍同意:以「用戶端向伺服器請求服務」的方式思考,有助於理解與管理分散式系統的複雜度

  • 在基本的**主從式模型(client-server model)**中,行程分成兩個(可能重疊的)群體:
    • 伺服器(server):實作某個特定服務(如檔案系統服務、資料庫服務)的行程。
    • 用戶端(client):送出請求並等待伺服器回覆的行程。
  • 這種互動又稱為請求—回覆行為(request-reply behavior)

圖 2-3:用戶端與伺服器之間的一般互動。

通訊協定的選擇#

  • 底層網路夠可靠時(如許多區域網路),可用簡單的無連線協定:用戶端把服務名稱與輸入資料包成訊息送出,伺服器處理後回覆。效率高是明顯優點。
  • 問題在於面對偶發傳輸失敗並不容易:用戶端沒收到回覆時只能重送,但它無法分辨是「請求遺失」還是「回覆遺失」。若回覆遺失而重送請求,操作會被執行兩次。
  • 可以無害地重複執行多次的操作稱為冪等(idempotent),例如「查詢餘額」;「轉帳一萬元」則不是——後者寧可回報錯誤也不該貿然重送。既然有些請求冪等、有些不是,遺失訊息就沒有單一解法。
  • 另一種做法是採用可靠的連線導向協定:在區域網路因效能相對低而不太合適,但在通訊本質上不可靠的廣域系統中運作得很好——幾乎所有 Internet 應用協定都建立在可靠的 TCP/IP 連線上。代價是連線的建立與拆除相對昂貴,尤其當請求與回覆訊息都很小的時候。

「沒收到回覆就重送」只對冪等操作安全。判斷一個操作能否重試,是設計請求—回覆協定時必須先回答的問題。

應用分層(application layering)#

主從式模型長年的爭論之一是如何清楚劃分用戶端與伺服器——實務上常常分不清楚,例如分散式資料庫的伺服器可能持續把請求轉給各檔案伺服器,自己其實只是在處理查詢,同時也在扮演用戶端。

考量許多主從式應用是為了支援使用者存取資料庫,普遍主張依循分層架構風格區分三個層次:

  1. 使用者介面層(user-interface level):與使用者直接互動所需的一切(如顯示管理),通常由用戶端實作。介面的精緻程度差異很大——從大型主機環境的純文字終端,到 X-Windows 這類圖形介面,再到能讓多個應用共用視窗、以拖曳交換資料的現代介面。
  2. 處理層(processing level):通常包含應用程式的核心功能。
  3. 資料層(data level):管理實際被操作的資料。

資料層的重點:

  • 資料通常是持久的(persistent):即使沒有應用程式在跑,資料仍儲存起來供下次使用。最簡單的形式是檔案系統,更常見的是完整的資料庫。
  • 資料層也負責維持資料在不同應用之間的一致性:包括表格描述、輸入限制、應用專屬的中繼資料等,例如用資料庫觸發器(trigger)在客戶卡債達到某個門檻時發出通知。
  • 商業環境多採關聯式資料庫,重點在資料獨立性(data independence):資料組織與應用程式互不影響,有助於把處理層與資料層分離。
  • 但關聯式不一定是最佳選擇:許多應用操作的複雜資料型別(從多邊形到 CAD 的飛機設計)更適合以物件建模,此時物件導向或物件關聯式資料庫更合理,後者因為建立在普及的關聯式模型上而漸受歡迎。
延伸案例:三個「處理層」的實例
  • 搜尋引擎:介面極簡(輸入關鍵字、回傳網頁標題清單),後端是預先抓取並建索引的龐大網頁資料庫;核心是把關鍵字字串轉成資料庫查詢、將結果排序並轉成 HTML 頁面的程式——這個資訊檢索部分就位於處理層。
  • 股票經紀決策支援系統:前端使用者介面、後端金融資料庫,中間是分析程式;金融分析可能用到統計與人工智慧的複雜方法,核心甚至需要跑在高效能電腦上才能達到使用者期望的吞吐量與回應性。
  • 桌面辦公套件:文書處理、試算表、通訊功能等,透過支援複合文件的共同介面整合,操作使用者家目錄(辦公環境中常放在遠端檔案伺服器)的檔案;此時處理層由一大批處理能力各自簡單的程式組成。

圖 2-4:把 Internet 搜尋引擎簡化組織成三個不同的層次。

多層式架構(multitiered architectures)#

三個邏輯層次暗示了把主從式應用實體分布到多台機器的多種可能:

  • 最簡單的組織只有兩種機器——用戶端機器只放(部分)使用者介面層,伺服器機器放其餘的處理層與資料層,即**(實體)二層式架構(two-tiered architecture)**。
  • 沿著「用戶端機器承擔多少」可排出五種常見切法:
    1. 只把終端相關的介面部分放在用戶端,應用程式遠端控制展示。
    2. 整個使用者介面軟體放在用戶端,成為透過應用專屬協定與伺服器溝通的圖形前端,前端除了展示介面不做其他處理。
    3. 把部分應用邏輯也搬到前端,例如「表單填完才能送出」的正確性/一致性檢查,或文書處理器把基本編輯放在本機、拼字文法檢查留在伺服器。
    4. 大部分應用都在用戶端機器上跑,只有對檔案或資料庫的操作送到伺服器,例如許多銀行應用讓使用者在本機準備好交易後才上傳到銀行的資料庫。
    5. 連部分資料都放在用戶端本機磁碟,例如瀏覽器在本機磁碟逐步累積最近瀏覽網頁的大快取。

圖 2-5:主從式組織的幾種替代方案 (a)–(e)。

  • 趨勢:近年在用戶端軟體裝在終端使用者機器上的情境,有一股明顯回頭潮——遠離第 4、5 種組織,把多數處理與資料儲存放回伺服器端。原因很簡單:胖客戶端(fat clients)雖然能做很多事,卻更難管理——功能越多,用戶端軟體越容易出錯、越依賴底層平台。**瘦客戶端(thin clients)**管理上容易得多,代價可能是較不精緻的介面與用戶端感受的效能。
  • 這個趨勢不代表不再需要分散式系統。相反地,伺服器端本身正變得越來越分散:單一伺服器被多台機器上的多個伺服器取代。伺服器有時也要扮演用戶端,於是形成**(實體)三層式架構(three-tiered architecture)**:處理層的程式放在獨立的伺服器上,也可能部分分布到用戶端與伺服器機器。
  • 三層式的典型場景:
    • 交易處理:由獨立的交易處理監視器(transaction processing monitor)協調跨多個資料伺服器的交易。
    • 網站組織:Web 伺服器作為網站入口,把請求轉給實際處理的應用伺服器,應用伺服器再與資料庫伺服器互動(例如電子書店查詢庫存的程式)。

圖 2-6:伺服器同時扮演用戶端的例子。

去中心化架構#

多層式主從架構是把應用切成使用者介面、處理元件、資料層的直接結果,各層對應應用的邏輯組織。這種「把邏輯上不同的元件放到不同機器」的分布方式稱為垂直分布(vertical distribution),概念上與分散式關聯資料庫的垂直分割(表格按欄切開分散)相關;從系統管理角度看有其好處——每台機器針對特定功能群調校。

  • 現代架構往往更在意水平分布(horizontal distribution):把用戶端或伺服器實體切成邏輯上等價的部分,各自處理完整資料集的自己那一份,藉此平衡負載。
  • 支援水平分布的現代系統架構,就是點對點系統(peer-to-peer systems)
    • 從高層視角看,組成系統的行程一律平等,系統該做的功能由每個行程共同承擔。
    • 互動大量是對稱的:每個行程同時扮演用戶端與伺服器(也稱作 servent)。
  • 因此點對點架構的核心問題是:如何把行程組織成覆蓋網路(overlay network)——節點是行程、連結是可用的通訊通道(通常實作為 TCP 連線)。一般而言行程不能直接與任意行程通訊,必須透過可用的通道傳訊。覆蓋網路分結構化非結構化兩類。

結構化點對點架構#

覆蓋網路以確定性程序建構,最常用的做法是分散式雜湊表(distributed hash table, DHT)

  • 資料項從大的識別碼空間(如 128 或 160 位元)取得隨機鍵值;節點也從同一空間取得隨機識別碼。
  • 每個 DHT 系統的關鍵,是實作一個高效且確定性的方案,依某種距離度量把資料鍵值唯一對應到節點識別碼。查詢資料項時,回傳的是負責該資料項的節點網路位址——實際上就是把請求路由到負責節點。
  • 查詢並不是沿邏輯環逐一走訪:每個節點會維護通往其他節點的捷徑,使查詢一般可在 O(log N) 步內完成(N 為參與節點數)。

Chord 系統為例:節點在邏輯上組成環,鍵值為 k 的資料項對應到識別碼不小於 k 的最小節點,稱為 k後繼者(successor),記為 succ(k)。應用在任一節點呼叫 LOOKUP(k) 取得 succ(k) 的網路位址後,即可聯繫該節點取得資料副本。

圖 2-7:Chord 中資料項對應到節點的方式。

延伸:Chord 與 CAN 的節點加入/離開

Chord 的成員管理

  • 加入:新節點產生隨機識別碼 id(識別碼空間夠大且亂數品質好時,撞號機率趨近於零),對 id 做一次查詢取得 succ(id) 的位址,聯繫 succ(id) 與其前驅者把自己插進環裡(因此每個節點也要記錄前驅者)。插入後,鍵值現在歸屬節點 id 的資料項會從 succ(id) 轉移過來。
  • 離開:節點 id 通知前驅者與後繼者,並把資料項轉給 succ(id),一樣簡單。

CAN(Content Addressable Network)

  • 部署一個 d 維笛卡兒座標空間,完整分割給所有參與節點;每個資料項對應空間中唯一一點,落在哪個節點的區域就由誰負責(落在邊界的用確定性規則指派)。
  • 加入:節點 P 隨機挑一點,透過位置式路由找到該點所在區域的節點 Q;Q 把自己的區域對半切,一半分給 P。節點都記錄自己的鄰居(負責相鄰區域的節點),P 向 Q 詢問即可得知新鄰居。資料項同樣從 Q 轉移。
  • 離開比較麻煩:離開節點的區域會指派給某個鄰居,但兩塊區域往往無法合併成矩形,只能由該鄰居代管並通知舊鄰居。這會使空間分割逐漸不對稱,因此需要定期啟動背景行程重新分割整個空間。

圖 2-8:(a) CAN 中資料項對應到節點的方式。(b) 節點加入時區域的分割。

非結構化點對點架構#

非結構化系統大量依賴隨機化演算法建構覆蓋網路:每個節點維護一份鄰居清單,但清單是以近乎隨機的方式建立;資料項也假設是隨機放置在節點上。

因為沒有確定性的方式能把查詢路由到特定資料項,節點要找資料時,實際上只能對網路**洪泛(flood)**搜尋請求。這是非結構化系統與結構化系統最根本的差別。

許多非結構化系統的目標是讓覆蓋網路近似隨機圖:每個節點維護 c 個鄰居的清單,理想上每個鄰居都是從目前節點集合中隨機選出的存活節點。這份清單稱為部分視圖(partial view)

Jelasity 等人提出一個能涵蓋多種覆蓋建構演算法的框架:節點定期交換部分視圖中的項目,每個項目標識網路中另一個節點,並帶有表示參照新舊程度的年齡(age)。運作由兩個執行緒構成:

主動執行緒(定期執行):
  從目前的部分視圖選出節點 P
  若為 PUSH 模式:
    建立緩衝區 = [(自己的位址, 0)] + 部分視圖打亂後的前 c/2 筆
    (先把 H 筆最舊的移到最後)
    將緩衝區送給 P
  否則:送出觸發訊息給 P
  若為 PULL 模式:接收 P 的緩衝區
  由目前視圖與 P 的緩衝區建構新的部分視圖
  將新視圖中每筆項目的年齡加一

被動執行緒:
  收到任一行程 Q 的緩衝區
  若為 PULL 模式:比照主動執行緒建立並回送緩衝區
  由目前視圖與收到的緩衝區建構新的部分視圖
  將每筆項目的年齡加一

圖 2-9:(a) 主動執行緒執行的步驟。(b) 被動執行緒執行的步驟。

  • 關鍵在新部分視圖的建構(雙方各保留恰好 c 筆):一種做法是丟棄送給對方的項目(等於交換部分視圖);另一種是盡量丟棄最舊的項目。兩種做法互補。
  • 幾個重要觀察:
    • 假設新節點透過某個知名接入點加入。只用 push 或只用 pull 模式的協定,很容易導致覆蓋網分裂——出現永遠無法互通的孤島。因此讓節點真正「交換」項目才合理。
    • 只要節點定期交換部分視圖,離開網路變得極簡單:直接離開即可,不必通知任何節點。當節點 P 發現鄰居 Q 不再回應,就把該項目從部分視圖移除;若採「盡量丟舊項目」策略,離開的節點會很快被全網遺忘。
    • 但這個策略有代價:以「有多少節點的部分視圖裡有 P」定義 P 的入度(indegree)——入度越高,P 被聯繫的機率越高,可能變成負載失衡的熱門節點。系統性丟棄舊項目正好會催生高入度節點

覆蓋網路的拓撲管理#

結構化與非結構化並非壁壘分明:謹慎地交換與挑選部分視圖中的項目,就能建構並維持特定拓撲的覆蓋網路。做法是兩層式:

  • 最下層:非結構化點對點系統,節點定期交換部分視圖,目標是維持一個準確的隨機圖(視圖中都是隨機選出的存活節點)。
  • 上層:接收下層傳上來的部分視圖,做額外的挑選,得到符合目標拓撲的第二份鄰居清單。挑選可用排名函數(ranking function)——例如按「與節點 P 的距離」遞增排序,P 便會在下層持續供應隨機節點的前提下,逐步累積出最近鄰清單。

圖 2-10:借用非結構化點對點系統的技術,以兩層式做法建構並維持特定的覆蓋網路拓撲。

圖 2-11:使用兩層式做法產生特定的覆蓋網路。

延伸案例:用排名函數長出環面(torus)與語意覆蓋網
  • 考慮 N×N 的邏輯格點,每個格點放一個節點,每個節點須維護 c 個最近鄰;(a₁, a₂) 與 (b₁, b₂) 的距離定義為 d₁+d₂,其中 dᵢ = min(N−|aᵢ−bᵢ|, |aᵢ−bᵢ|)。只要最下層定期執行前述交換協定,演化出的拓撲就是環面(torus)
  • 排名函數也可以完全不同,特別有趣的是捕捉節點所存資料項語意接近度(semantic proximity)的函數:由此可建構語意覆蓋網路(semantic overlay networks),讓非結構化系統擁有高效率的搜尋演算法。

超級對等節點(superpeers)#

尤其在非結構化系統中,網路一大,定位資料就成問題——沒有確定性路由,只能洪泛。除了各種抑制洪泛的技巧,許多點對點系統提出用特殊節點維護資料項索引的替代方案。

  • 其他放棄對稱性的合理情境:例如協作式內容傳遞網路(CDN)中,節點互相提供儲存空間存放網頁副本;此時用一個**中介者(broker)**收集鄰近一批節點的資源使用狀況,就能快速選出資源充足的節點。
  • 維護索引或擔任中介者的節點通稱超級對等節點(superpeer)。超級對等節點本身也常組成點對點網路,形成階層式組織:每個一般節點以用戶端身分連到某個超級對等節點,一般節點的所有進出通訊都經由它。

圖 2-12:把節點階層式組織成超級對等節點網路。

  • 實務考量:
    • 用戶端與超級對等節點的關係常是固定的:加入時掛上某個超級對等節點,直到離開。因此超級對等節點必須是長壽命、高可用的行程;為了補償其潛在的不穩定,可部署備援方案,例如把超級對等節點兩兩配對,要求用戶端同時掛在兩邊。
    • 固定關係不一定最好:檔案分享網路中,用戶端最好掛在「索引內容符合自己興趣」的超級對等節點上,找檔案時命中率較高。有一種簡單方案讓用戶端發現更好的超級對等節點時可以換掛——特別是回傳過查詢結果的超級對等節點會被優先考慮。
    • 新問題:該選哪些節點當超級對等節點? 這與領導者選舉(leader election)問題密切相關。

混合式架構#

許多分散式系統結合多種架構特徵——超級對等網路已是一例。以下是幾類把主從式方案與去中心化架構結合的系統。

邊緣伺服器系統(edge-server systems)#

  • 部署於 Internet,伺服器放在網路「邊緣」——企業網路與 Internet 的交界,例如由 ISP 提供;家庭使用者經 ISP 上網,ISP 也可視為位於 Internet 邊緣。
  • 終端使用者(或一般的用戶端)透過邊緣伺服器連上 Internet。邊緣伺服器的主要用途是提供內容,可能先經過過濾與轉碼(transcoding)。
  • 更有趣的是一群邊緣伺服器可以合力最佳化內容與應用的散布:基本模型是特定組織有一台邊緣伺服器作為所有內容的源頭伺服器(origin server),再利用其他邊緣伺服器複寫網頁等內容。

圖 2-13:把 Internet 視為由一群邊緣伺服器組成。

協作式分散式系統#

混合結構特別常見於協作式系統:啟動階段多半採傳統主從式方案,節點加入後才改用完全去中心化的協作方案。

BitTorrent(點對點檔案下載系統):

  • 基本想法:使用者從其他使用者那裡下載檔案的分塊(chunks),湊齊後組回完整檔案。
  • 下載流程:先到全域目錄(少數幾個知名網站)找到 .torrent 檔;該檔案包含下載所需資訊,特別是指向追蹤器(tracker)——一台精確記錄「哪些活躍節點持有該檔案哪些分塊」的伺服器。追蹤器很多,但每個檔案(或檔案集合)通常只有一個。
  • 設計目標是強制協作:多數檔案分享系統中,相當比例的參與者只下載、幾乎不貢獻。BitTorrent 規定只有在提供內容給別人時才能下載——若節點 P 發現節點 Q 下載得多、上傳得少,P 可以降低送資料給 Q 的速率(「以牙還牙(tit-for-tat)」)。此機制在 P 有東西可向 Q 下載時才有效,因此節點常被提供大量其他節點的參照,以便處於更好的交換位置。
  • BitTorrent 明顯結合了集中式(目錄、追蹤器)與去中心化(分塊交換)方案;系統瓶頸不意外地落在追蹤器

圖 2-14:BitTorrent 的主要運作方式。

Globule(協作式內容傳遞網路):

  • 與邊緣伺服器架構神似,但由終端使用者(或組織)自願提供加強型 Web 伺服器,協作複寫網頁。最簡形式下,每台伺服器有三個元件:把用戶端請求轉向其他伺服器的元件、分析存取模式的元件、管理網頁複寫的元件。
  • 平常處理 Alice 網站流量的伺服器是該站的源頭伺服器;它與其他伺服器(例如 Bob 提供的)協作,代管彼此的頁面——這一面是去中心化的。對 Alice 網站的請求先送到她的伺服器,再視情況轉向其他伺服器。
  • 但 Globule 也有集中式元件——中介者(broker):負責註冊伺服器並讓大家知道彼此,伺服器與中介者的通訊完全就是主從式。為了可用性,中介者可以複寫。