至此我們看過針對「每秒請求數」擴展的複製模式,以及針對「資料大小」擴展的分片模式。分散/收集模式(scatter/gather pattern)則是用複製來換取時間上的擴展:它讓請求的處理平行化,遠快於循序處理。

  • 與複製式和分片式系統一樣,這是一種樹狀模式:root 分發請求、葉節點(leaf)處理請求。
  • 不同之處在於:分散/收集把請求同時發給系統中所有副本,每個副本做一小部分處理、回傳一小部分結果,root 再把各個部分結果合併成單一完整回應送回客戶端。
  • 適用時機:處理單一請求需要大量彼此大致獨立的運算。可以把它看成是對「服務請求所需的運算」分片,而非對資料分片(雖然資料分片也可能是其中一環)。

圖 7-1:分散/收集模式

Root 分發式分散/收集#

  • 最簡單的形式:每個葉節點完全同質,工作分發給多個葉節點只為提升請求效能——等同於解一個「尷尬平行(embarrassingly parallel)」問題:問題能拆成許多片、各片再拼回完整答案。
  • 具體化:請求 R 用單核心要花一分鐘算出答案 A。用 30 核心的多執行緒程式可把時間壓到 2 秒(60 秒 ÷ 30 執行緒)——但對 Web 請求來說 2 秒仍太慢,而且單一行程要真正達到完全平行加速很難:記憶體、網路、磁碟頻寬會開始成為瓶頸。
  • 改用分散/收集把請求平行化到多台機器的多個行程
    • 延遲不再受限於單機核心數。
    • 記憶體、網路、磁碟頻寬分散在多台機器上,瓶頸得以維持在 CPU。
    • 樹中每台機器都能處理任何請求,root 可以動態調度:某個葉節點回應變慢時(例如被吵鬧鄰居行程干擾資源),root 能動態重新分配負載以確保快速回應。
動手做:分散式文件搜尋(詞彙分散)

任務:在大型文件庫中搜尋同時包含 “cat” 與 “dog” 的所有文件。逐一開檔全文搜尋太慢,因此先建索引(index)——實質上是一張雜湊表,鍵是單字(如 “cat”)、值是包含該字的文件清單。

索引只有單字、沒有字的連言(conjunction),所以「同時包含兩個字」的文件要靠集合交集求得。以分散/收集實作:請求進到搜尋 root 後被解析、分發給兩台葉機器(一台查 “cat”、一台查 “dog”);各自回傳符合單字的文件清單,root 回傳兩者的交集。

圖 7-2:詞彙分片的分散/收集系統範例

例:查 “cat” 的葉節點回傳 {doc1, doc2, doc4},查 “dog” 的回傳 {doc1, doc3, doc4},root 求交集後回傳 {doc1, doc4}

圖 7-3:連言查詢在分散/收集搜尋系統中的執行過程

葉節點分片式分散/收集#

  • 複製資料的分散/收集能縮短處理時間,但無法擴展到超過單機記憶體或磁碟能容納的資料量——到了某個資料規模,就必須引入分片。
  • 與先前「按請求分片」(用請求的某部分決定送去哪個副本、該副本包辦全部處理)不同,分散/收集的分片是:請求送給系統中所有葉節點(分片),每個葉節點用自己分片內載入的資料處理請求,把部分回應交回 root,root 再合併所有回應成完整回應。
  • 例子:對超大文件集(如全世界所有專利)建搜尋。資料大到單機記憶體放不下,於是把資料分片到多個副本——例如專利 0–100,000 在第一台、100,001–200,000 在第二台,依此類推。使用者查詢某個字(如 “rockets”)時,請求送到每個分片,各分片在自己的專利分片中搜尋符合的專利並回傳,root 把所有回應整理成包含所有符合專利的單一回應。

書中特別註明:「依專利號區間分片」其實不是好方案——新專利登記時會不斷被迫增加新分片。實務上比較可能用專利號對分片總數取模

動手做:分片文件搜尋

前一個例子把不同詞彙的請求分散到叢集,但這只在所有文件都存在於所有機器上時可行。若葉節點放不下全部文件,就必須分片,把不同的文件集放到不同葉節點。

此時使用者查「包含 cat 與 dog 的所有文件」,請求會送到每一個葉節點;每個葉節點回傳它所知符合 “cat” 與 “dog” 的文件集。與先前不同:root 的責任從「求兩個詞彙結果的交集」變成「求所有分片回傳文件的聯集」,再把完整文件集回傳給使用者。

例:第一葉(服務文件 1–10)回傳 {doc1, doc5};第二葉(文件 11–20)回傳 {doc15};第三葉(文件 21–30)回傳 {doc22, doc28};root 合併回傳 {doc1, doc5, doc15, doc22, doc28}

選對葉節點數量#

直覺上把葉節點複製得越多越好——平行度越高、時鐘時間越短。但平行化是有代價的,選對葉節點數量是設計高效能分散式系統的關鍵。兩個原因:

  • 每個節點的固定開銷:解析請求、走 HTTP 線路等。這種系統開銷通常是常數、遠小於處理請求的使用者程式碼時間,單看可以忽略;但它隨葉節點數量線性增長——平行化持續下去,開銷終將壓過業務邏輯的運算成本。平行化的收益是漸近的(asymptotic)
  • 落後者問題(straggler problem):root 必須等所有葉節點回應才能回覆使用者,因此整體時間由最慢的葉節點決定。
    • 數字:某服務第 99 百分位延遲是 2 秒(1% 的請求要 2 秒)。單看可接受;但分散到 5 個葉節點時,「五個請求中有一個達 2 秒」的機率是 5%(0.99⁵ ≈ 0.95)——單一請求的 99 百分位延遲,變成了整個系統的 95 百分位延遲。分散到 100 個葉節點,幾乎保證每個請求的整體延遲都是 2 秒。

分散/收集系統的三個結論:

  • 因為每個節點的開銷,提高平行度不一定變快。
  • 因為落後者問題,提高平行度不一定變快。
  • 第 99 百分位的效能比在其他系統中更重要——因為每個使用者請求實際上會變成對服務的大量請求。

落後者問題同樣適用於可用性:對 100 個葉節點發請求、單一葉節點故障機率為 1/100 時,幾乎保證每一個使用者請求都會失敗。

為可靠性與規模擴展分散/收集#

單一副本的分片分散/收集系統有明顯缺陷:

  • 任一分片故障期間,所有分散/收集請求都會失敗(因為每個請求都需要所有葉節點參與)。
  • 升級會拿掉一部分分片,無法在使用者流量下升級
  • 系統的運算規模受限於單一節點的負載能力——而且如前所述,增加分片數並不能提升分散/收集模式的運算力。

正確做法:複製每一個分片——每個葉分片不再是單一實例,而是一個複製式服務。

圖 7-4:分片且複製的分散/收集系統

這樣一來:

  • root 對每個葉分片的請求,實際上是在該分片所有健康副本間負載平衡,故障不會造成使用者可見的中斷。
  • 可以在負載下安全升級:每個複製式分片一次升級一個副本;視你想要的升級速度,甚至可以同時跨多個分片進行。