01

為什麼研究 Google:需求與挑戰

用『distributed systems book』這個查詢,走一遍 Googlebot 爬取、倒排索引、PageRank 排名的完整流程。

先讀原文開場,旁邊就是白話

這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。

原文 · 設計分散式系統 915 21 DESIGNING DISTRIBUTED SYSTEMS: GOOGLE CASE STUDY 21. 2 Introducing the case study: Google 21. 3 Overall architecture and design philosophy 21. 4 Underlying communication paradigms 21.
白話導讀

用『distributed systems book』這個查詢,走一遍 Googlebot 爬取、倒排索引、PageRank 排名的完整流程。

搜尋引擎的三步驟:爬取、索引、排名

用『distributed systems book』這個查詢,走一遍 Googlebot 爬取、倒排索引、PageRank 排名的完整流程。

STEP 1

深度探秘

一個查詢背後的三件大事

你打字之前,Google 早就忙了好幾週

當你搜尋 distributed systems book,0.2 秒內就拿到結果。但這背後其實有三個獨立的工作,而且大部分在你打字之前就完成了:

  • 爬取 (Crawling):派出一隻叫 Googlebot 的軟體機器人,沿著網頁上的超連結一頁一頁讀過去。讀到一頁就把上面的連結收集起來,再排程去爬那些連結——這種一路往下挖的方式叫做 deep searching(深度搜尋),幾乎能觸及全網。
  • 索引 (Indexing):光是把網頁抓回來還不夠。索引會建立一份 inverted index(倒排索引),把『一個字 → 它在哪些文件的哪些位置出現』整理成一張可快速查詢的表。
  • 排名 (Ranking):找得到還不夠,要先給你最重要的。PageRank 演算法負責決定哪一頁該排在前面。

重點:找一個關鍵字其實不難,難的是在數十億頁中,又快又準地挑出最該給你看的那幾頁。

flowchart TD
  A[Googlebot 爬取網頁] --> B[建立倒排索引]
  B --> C[PageRank 排名]
  C --> D[回傳排序後的結果清單]
💡
關鍵

搜尋=爬取(找到頁面)+索引(記住字在哪)+排名(決定誰最重要)三件事的組合。

STEP 2

生活妙喻

倒排索引就像一本書最後面的索引頁

想像一本超級厚的書

一本書最後面通常有「索引」:你想找『遞迴』,翻到索引就看到「遞迴 …… 頁 88、142、201」。你不必從第一頁翻到最後一頁。倒排索引就是這個概念,但規模是整個網際網路。

概念 書本索引 Google 倒排索引
索引項 一個詞 一個字(含 PDF、Word 等文件裡的字)
指向 頁碼 出現在哪個文件、哪個位置
額外資訊 字體大小、是否大寫(用來判斷重要性)

那 PageRank 像什麼? 像學術論文的「被引用次數」。一篇論文被很多人引用,代表它重要。一個網頁被很多頁連結,也代表它重要——而且連結來源本身越重要,這個連結就越有份量。來自 bbc.co.uk 的連結,比來自某個人個人首頁的連結更有說服力。

所以 Google 不是問『這頁有沒有這些字』,而是問『這頁有這些字,而且全網有多看重它?』

💡
關鍵

倒排索引=書末索引的全網版;PageRank=用『誰連結你、誰又多重要』來模仿學術引用的影響力。

STEP 3

實用超能力

從數十億頁縮到幾頁的真實過程

跟著 distributed systems book 走一遍

  1. 拆字查索引:分別查 distributedsystemsbook 三個字,各自得到一大堆出現它們的頁面。
  2. 取交集:找出同時包含三個字的頁面,例如 amazon.comwww.cdk5.net。光這步就能把候選從數十億頁砍到大概幾萬頁。
  3. 排名:對這幾萬頁算重要性——
    • 被很多『重要網站』連結的頁面(如 amazon 的書頁)往上排;
    • 三個字靠得很近的頁面往上排(代表真的在講這個主題);
    • 字出現在頁面開頭或用大寫/大字體的頁面往上排。
  4. 回傳:最重要的排最上面,於是你第一眼就看到最相關的結果。

Caffeine 與 Percolator:早期爬取是『每幾週批次跑一次』,但突發新聞、股價需要更即時。2010 年 Google 用 Caffeine 改成持續性的增量更新,底層靠 Percolator 對大型資料集做增量更新,讓搜尋結果更新鮮。

💡
關鍵

查字→取交集(數十億縮到數萬)→依重要性排名→回傳,是把『大海撈針』變成『精準命中』的實際步驟。

🔆
生活妙喻:倒排索引 (inverted index) ≈ 書本最後面的索引頁

與其逐頁翻找一個詞,不如反過來建一張『詞 → 出現在哪些位置』的表,查一次就知道去哪找。

🔆
生活妙喻:PageRank ≈ 學術論文的被引用次數

論文被越多(且越權威的)論文引用就越重要;網頁被越多(且越重要的)網頁連結就排越前面。

🔆
生活妙喻:Googlebot 的 deep searching ≈ 順著線頭把整團毛線拉出來

從一頁的連結爬到下一頁,再從那頁的連結繼續,沿著連結網路幾乎能走遍整個網。

本節字彙

Crawling(爬取)
由 Googlebot 自動讀取網頁內容、收集連結並排程後續爬取的過程。
🧠 想像一隻蜘蛛沿著蜘蛛網(連結)一格一格爬。
Inverted index(倒排索引)
把『字 → 出現位置』反向建立的查詢表,是快速搜尋的核心。
🧠 正常是『文件裡有哪些字』,倒過來變『一個字在哪些文件』。
PageRank
依據『被哪些頁連結、來源又多重要』來評估網頁重要性的排名演算法。
🧠 Page 既是創辦人 Larry Page,也是『頁面』的排名。
某搜尋引擎能正確找出所有含關鍵字的頁面,但使用者抱怨『最該看的結果總是埋在很後面』。最可能是哪個環節做得不夠好?
為什麼 Google 會認為『來自 bbc.co.uk 的連結』比『來自某人個人首頁的連結』更有價值?
倒排索引最關鍵的『反向』體現在哪裡?

四大設計需求:規模、可靠、效能、開放

理解 Google 為何把可擴展性拆成『更多資料、更多查詢、更好結果』三維度,以及可靠性、效能、開放性各自意味著什麼。

STEP 1

深度探秘

撐起 Google 的四根柱子

設計之前,先搞懂『要解什麼問題』

所有後面的技術選擇,都是為了滿足這四個需求。先記住它們,後面每個服務都會回頭呼應:

  • 可擴展性 (Scalability):Google 是所謂的 ULS(Ultra-Large Scale,超大規模) 系統。它把擴展拆成三個維度
    1. 更多資料(網路一直變大、圖書館數位化);
    2. 更多查詢(用的人越來越多,到 2010 年底每月超過 880 億次查詢);
    3. 更好結果(搜尋品質是用戶會不會繼續用的關鍵)。
  • 可靠性 (Reliability):要 24/7 不中斷。Google Apps 對付費客戶提供 99.9% 的服務水準協議 (SLA)
  • 效能 (Performance):目標是 0.2 秒回傳搜尋結果。效能是端到端 (end-to-end) 的,網路、儲存、運算要一起到位。
  • 開放性 (Openness):基礎設施要可擴充,方便不斷長出新的 Web 應用。
flowchart TD
  S[可擴展性] --> A[更多資料]
  S --> B[更多查詢]
  S --> C[更好結果]
💡
關鍵

規模、可靠、效能、開放是四大需求;其中『可擴展性』要拆成更多資料、更多查詢、更好結果三個方向來看。

STEP 2

生活妙喻

經營一家永不打烊的超大連鎖餐廳

把 Google 想成一家全球連鎖餐廳

需求 餐廳比喻 為什麼難
可擴展性 同時要應付『食材更多、客人更多、菜要更好』 三件事一起成長,不能顧此失彼
可靠性 24 小時不打烊,連停電都要照常出餐 要假設『設備一定會壞』還能營業
效能 點完餐 0.2 秒上菜 廚房、外場、收銀全鏈條都要快
開放性 隨時能加新菜色、新分店 後場系統要好擴充

一個重要洞見:可靠性必須在『設備經常會壞』的前提下達成。Google 用便宜 PC,每天約有 20 台因軟體問題要重開機。它不是去買貴一點、更不容易壞的設備,而是設計成『壞了也沒關係,系統自己撐住』

與其追求『永不故障的零件』,不如打造『會故障也不怕的系統』。

💡
關鍵

可靠性不是靠買更貴的零件,而是在『故障一定會發生』的假設下,靠設計與冗餘把故障遮蔽掉。

STEP 3

實用超能力

用『信封背面的計算』感受規模

為什麼非得用分散式不可?

Jeff Dean 在演講裡做過一個經典的『back of the envelope(信封背面)』估算:

  • 假設全網約 200 億頁,每頁 20 KB,總量約 400 TB
  • 一台電腦若以 30 MB/s 讀取,要爬完這些資料得花超過 4 個月
  • 1000 台機器並行,只要不到 3 小時

這就是為什麼爬取、索引、排名、搜尋全部都得是高度分散式的解法——單機根本不可能在合理時間內完成。

真實世界的提醒:2009 年 9 月 1 日 Gmail 曾中斷 100 分鐘,原因是例行維護期間伺服器過載引發連鎖反應。即使設計再好,可靠性仍是持續的挑戰。

搜尋的一個小秘密:搜尋的失敗其實『天生容易遮蔽』——因為使用者根本無從得知是不是所有相關結果都回傳了。這讓搜尋在可靠性上比金融交易這類應用稍微寬鬆一點。

💡
關鍵

用簡單估算就能看出單機不可行(4 個月 vs 3 小時),這正是『非分散式不可』的根本理由;同時即使設計再好,可靠性仍需持續對抗連鎖故障。

🔆
生活妙喻:ULS 超大規模系統 ≈ 全球連鎖、24 小時不打烊的餐廳

要同時應付食材更多、客人更多、菜色更好,還得在設備會壞的情況下照常營業。

🔆
生活妙喻:可靠性建立在『故障必然發生』上 ≈ 汽車的備胎與安全氣囊

你不是假設輪胎永不爆,而是準備好爆了也能繼續上路的機制。

🔆
生活妙喻:back of the envelope 估算 ≈ 出門前心算『開車還是搭捷運比較快』

不用精算,粗略估個量級就能立刻判斷哪條路可行——單機 4 個月 vs 千機 3 小時。

本節字彙

Scalability(可擴展性)
系統在資料量、請求量、品質要求上升時,仍能有效運作的能力。
🧠 Scale=量尺,能隨著需求一路放大刻度。
SLA(服務水準協議)
服務提供者對可用性等指標做出的保證,例如 Google Apps 的 99.9%。
🧠 Service Level Agreement=白紙黑字的『我保證做到這個水準』。
End-to-end 效能
效能是整條鏈路(網路、儲存、運算)共同決定的性質,任一環節慢都會拖累整體。
🧠 一條鏈條的強度取決於最弱的那一環。
Google 把『可擴展性』拆成三個維度。下列哪一組正確?
Google 選用便宜的 commodity PC,明知它們較常故障。這個決策背後的可靠性策略是什麼?
Jeff Dean 的估算指出單機爬完全網要 4 個月、千機只要不到 3 小時。這個對比最主要想說明什麼?
02

整體架構與設計哲學

為什麼 Google 選便宜的 commodity PC?機架、叢集、資料中心如何層層堆疊出 PB 級儲存與容錯能力。

用便宜 PC 堆出超級電腦:實體架構

為什麼 Google 選便宜的 commodity PC?機架、叢集、資料中心如何層層堆疊出 PB 級儲存與容錯能力。

STEP 1

深度探秘

為什麼選一堆便宜 PC,而不是一台超級電腦?

核心哲學:買『每塊錢的效能』

Google 的實體基礎設施有一個很反直覺的決定:用大量的 commodity PC(一般市售規格的便宜電腦),而不是少數昂貴的高階伺服器。每台大約 1,000 美元,配備約 2 TB 硬碟16 GB DRAM,跑精簡版的 Linux。

採購邏輯是 best performance per dollar(每塊錢的最佳效能),而不是絕對效能。

為什麼這樣划算? 看故障數據就懂了:

  • 軟體故障最常見:每天約 20 台因軟體問題要重開機(而且重開是純手動的!)。
  • 硬體故障只有軟體的約 1/10:每年約 2–3% 的 PC 因硬體壞掉,其中 95% 是硬碟或 DRAM 出問題。

既然絕大多數故障來自軟體,花大錢買更可靠的硬體並不值得——這就反過來證明了選便宜 PC 是對的

💡
關鍵

用大量便宜 PC 追求『每塊錢的效能』;因為多數故障來自軟體而非硬體,買貴硬體不划算。

STEP 2

生活妙喻

螞蟻雄兵 vs 一頭大象

一頭大象,還是一群螞蟻?

你要搬一座山的資料。你可以雇『一頭超強大象』(昂貴的超級電腦),也可以派『一百萬隻螞蟻』(便宜 PC 大軍)。

  • 大象很貴,而且牠一倒下,整個工作就停了
  • 螞蟻便宜又好補充死掉幾隻完全不影響整群繼續搬。

Google 選了螞蟻雄兵。為了管理這群螞蟻,牠們被組織成清楚的層次:

flowchart TD
  PC[便宜 PC] --> Rack[機架 40 到 80 台 PC]
  Rack --> Cluster[叢集 30 個以上機架]
  Cluster --> DC[資料中心 散布全球]
層次 規模 角色
PC 1 台 最小運算/儲存單位
機架 (Rack) 40–80 台 PC 用 Ethernet switch 連起來
叢集 (Cluster) 30+ 機架 管理的關鍵單位:決定服務放哪、複製幾份
資料中心 多個叢集 散布全球(美國、都柏林、蘇黎世、東京…)

為了容錯,每個機架接到兩個高頻寬交換器,每個交換器又有冗餘的對外連線

💡
關鍵

PC→機架→叢集→資料中心層層堆疊;叢集是管理的關鍵單位,靠雙交換器與冗餘連線達成容錯。

STEP 3

實用超能力

算一算 Google 到底能存多少

用乘法感受『天文數字』

讓我們順著層次算下去(這正是『信封背面估算』的精神):

  • 1 台 PC ≈ 2 TB
  • 1 個機架(80 台)≈ $80 \times 2 = 160$ TB
  • 1 個叢集(30 機架)≈ $30 \times 160 = 4800$ TB ≈ 4.8 PB
  • 假設約 200 個叢集 ≈ $200 \times 4.8 = 960$ PB ≈ 接近 1 EB(exabyte,$10^{18}$ bytes)

而這還被認為是保守估計

這給我們什麼超能力?

  1. 容錯的本錢:因為機器多、又有層次化的冗餘(雙交換器、複製服務),少數機器掛掉時,系統可以把工作轉移走,使用者完全無感。
  2. 管理的單位:以叢集為單位來決定『某個服務放在哪、要複製幾份』,讓大規模管理變得有章法。
  3. 就近部署:資料中心散布全球,讓服務更靠近使用者,降低延遲。
💡
關鍵

層層相乘讓儲存量達到近 1 EB 的天文等級;龐大數量加上層次化冗餘,正是容錯與大規模管理的本錢。

🔆
生活妙喻:commodity PC 大軍 ≈ 螞蟻雄兵而非一頭大象

一群便宜、可替換的螞蟻,死幾隻不影響大局;一頭昂貴大象倒下就全停擺。

🔆
生活妙喻:PC→機架→叢集→資料中心的層次 ≈ 士兵→班→連→軍團

把大量單位組織成清楚的層級,才管得動、調得動。

🔆
生活妙喻:雙交換器與冗餘連線 ≈ 重要房間有兩個出口

一條路被堵住,還有另一條可走,火災時不會困死。

本節字彙

Commodity PC
一般市售規格的便宜電腦,Google 用大量這種機器組成基礎設施。
🧠 Commodity=大宗商品,像米和糖一樣便宜又好買。
Cluster(叢集)
由 30 個以上機架組成的單位,是 Google 管理(決定服務放置與複製)的關鍵單位。
🧠 Cluster=一串葡萄,許多機架聚成一團一起管理。
Redundancy(冗餘)
刻意保留多份備援(複本、多條連線、多台機器),讓部分失效時仍能運作。
🧠 多備一份,壞了不慌。
故障數據顯示『軟體故障是硬體故障的約 10 倍』。這個事實如何支持『用便宜 PC』的決策?
在 Google 的實體層次中,被視為『管理的關鍵單位』、用來決定服務放置與複製份數的是哪一層?
若一台 PC 有 2 TB,一個機架有 80 台 PC,一個叢集有 30 個機架,那麼一個叢集的儲存量大約是多少?

三層系統架構與貫穿全公司的設計原則

底層平台、中間共用基礎設施、上層應用的三層結構;以及 simplicity、every millisecond counts、嚴格測試三大原則。

STEP 1

深度探秘

上中下三層,中間那層最關鍵

Google 系統的三明治結構

Google 的整體系統架構可以看成三層:

flowchart TD
  A[Google 應用與服務 搜尋 Gmail 地圖] --> B[Google 基礎設施 中介軟體 middleware]
  B --> C[Google 平台 便宜 PC 機架 叢集]
  • 最上層:大家熟悉的應用與服務(搜尋、Gmail、Docs、Maps…)。
  • 最下層:上一節講的實體平台(便宜 PC、機架、叢集)。
  • 中間層(最關鍵)共用的分散式基礎設施(中介軟體 middleware),提供搜尋與雲端共用的通訊、儲存、運算服務。

這個中間層封裝了處理可擴展性、可靠性、效能的關鍵策略。它的價值在於:

  1. 重用:新應用不必重造輪子,直接用底層服務就能快速 bootstrap。
  2. 一致性:強制大家用共同的策略與設計原則,讓越長越大的程式碼庫仍保持整體連貫。
💡
關鍵

三層架構中,中間的『共用基礎設施(中介軟體)』封裝了可擴展、可靠、高效能的策略,是 Google 成功的關鍵。

STEP 2

生活妙喻

共用基礎設施像城市的水電網路

為什麼開新店不用自己挖水井?

想像你要在一座城市裡開咖啡店。你不必自己挖水井、自己發電、自己鋪路——城市早就提供了水、電、道路這些共用基礎設施。你只要接上去就能營業。

Google 的中間層就是這座城市的水電網路:

  • 想做新應用(開新店)?直接接上 GFS(儲存)、Bigtable(資料庫)、MapReduce(運算)就好。
  • 大家都接同一套水電,整座城市(程式碼庫)才不會亂成一團。

而這座城市的『市政原則』有三條,貫穿整個 Google:

原則 一句話 生活對應
簡單 (Simplicity) 做好一件事,避免功能臃腫;API 越小越好(奧坎剃刀) 一把好用的瑞士刀,不如各司其職的單一工具
每一毫秒都重要 (every millisecond counts) 重視效能,要能估算基本操作的成本 出門前先心算各路線要多久
嚴格測試 (if it ain't broke, you're not trying hard enough) 拼命測,再配合 logging 與 tracing 抓 bug 飛機起飛前的層層檢查
💡
關鍵

共用基礎設施像城市水電,讓新應用直接接用;而簡單、效能、嚴格測試三大原則就像貫穿全城的市政準則。

STEP 3

實用超能力

簡單原則怎麼落實到日常設計

把『簡單』變成可操作的習慣

『簡單』聽起來很抽象,但 Google 把它落實成具體做法:

  • API 設計:Bloch 指出 API 應該『盡可能小,但不能更小』。少一個多餘的方法,未來就少一個維護與相容包袱。
  • 效能估算:Jeff Dean 強調工程師要能心算基本操作的成本——存取記憶體、讀硬碟、發一個網路封包、鎖/解鎖一個 mutex 各要多久。有了這些數字,就能做『信封背面』的設計評估,提早發現瓶頸。
  • 測試文化:口號是『if it ain't broke, you are not trying hard enough(沒測壞,就是你還不夠拚)』。配合大量 logging(日誌)與 tracing(追蹤),故障一發生就能快速定位根因。

這給你的超能力:當你看到後面 GFS、Chubby、Bigtable 的設計時,會發現它們反覆選擇『簡單但夠用』而非『功能齊全但複雜』——例如 Bigtable 不支援完整關聯式運算、Chubby 只做整檔讀寫。這不是偷懶,而是刻意的設計紀律

💡
關鍵

簡單原則化為具體習慣:API 盡量小、能估算操作成本、拼命測試+logging/tracing;後面的服務都會反覆呼應這套紀律。

🔆
生活妙喻:共用基礎設施(中間層) ≈ 城市的水電與道路網路

開新店不必自己挖井發電,接上共用基礎設施就能營業,整座城市也因此井然有序。

🔆
生活妙喻:簡單原則(奧坎剃刀) ≈ 盡可能小但不能更小的瑞士刀

只保留必要功能,多餘的就剃掉,減少維護與相容負擔。

🔆
生活妙喻:嚴格測試+logging/tracing ≈ 飛機起飛前的層層檢查清單

拼命找出可能壞掉的地方,並留下完整紀錄,出事時能快速回溯根因。

本節字彙

Middleware(中介軟體)
介於底層平台與上層應用之間的共用服務層,提供通訊、儲存、運算等共用能力。
🧠 Middle=中間,夾在應用與硬體之間的那層。
Simplicity(簡單原則)
Google 最重要的設計原則:做好一件事、避免臃腫、API 越小越好。
🧠 奧坎剃刀:能剃掉的複雜就剃掉。
Tracing(追蹤)
記錄請求在系統中流動的路徑與事件,配合 logging 用來偵測與定位故障。
🧠 像追蹤包裹,知道它經過了哪些站。
在 Google 三層架構中,被視為『成功關鍵』、封裝了可擴展性與可靠性策略的是哪一層?
把共用基礎設施比喻成『城市的水電網路』,最主要想強調它的什麼價值?
『API 應該盡可能小,但不能更小』這句話是哪一個設計原則的具體體現?
03

底層通訊範式

用一個簡單語言描述資料格式,編譯成程式碼,比 XML 小 3-10 倍、快 10-100 倍;以及『單一參數、單一回傳』的 RPC 風格。

Protocol Buffers 與精簡版 RPC

用一個簡單語言描述資料格式,編譯成程式碼,比 XML 小 3-10 倍、快 10-100 倍;以及『單一參數、單一回傳』的 RPC 風格。

STEP 1

深度探秘

讓不同機器講同一種『打包語言』

問題:機器之間怎麼傳遞結構化資料?

當一台機器要把『一本書的資料』傳給另一台,它得先把記憶體裡的物件攤平成一串位元組(這叫 serialization 序列化),對方再還原。XML 也能做,但它太『萬用』導致又肥又慢

Google 因此打造了 protocol buffers:一種語言中立、平台中立的序列化格式,號稱比 XML 小 3–10 倍、快 10–100 倍

你先用一個簡單語言寫 .proto描述訊息格式:

message Book {
  required string title = 1;
  repeated string author = 2;
  enum Status { IN_PRESS = 0; PUBLISHED = 1; OUT_OF_PRINT = 2; }
  message BookStats {
    required int32 sales = 1;
    optional int32 citations = 2;
    optional Status bookstatus = 3 [default = PUBLISHED];
  }
  optional BookStats statistics = 3;
  repeated string keyword = 4;
}

每個欄位有名字、型別、唯一編號(=1、=2…,這個編號是二進位編碼裡的 tag)。欄位標籤有三種:

  • required:一定要有;
  • optional:可有可無;
  • repeated:可出現 0 到多次(像動態陣列)。

接著用 protoc 工具編譯 .proto,自動產生程式碼(如 getTitle()setTitle()hasTitle()clearTitle()),讓你方便地存取訊息。

💡
關鍵

protocol buffers 用簡單的 .proto 語言描述資料格式,經 protoc 編譯成程式碼,提供比 XML 更小更快的序列化。

STEP 2

生活妙喻

宅配紙箱 vs 印滿說明的禮盒

寄包裹要用哪種箱子?

你要寄一份禮物。有兩種包裝:

  • XML:像一個印滿說明文字的精美禮盒,盒子上寫著『這是一個盒子,裡面第一格放卡片,第二格放…』。自我描述、誰拿到都看得懂(互通性好),但盒子本身又大又重。
  • Protocol buffers:像一個樸素的宅配紙箱,箱子上只有編號標籤(=1、=2)。收件人手上有同一份『編號對照表』(.proto),所以一看編號就知道內容。又小又快,但前提是雙方都得有對照表
比較 XML Protocol buffers
大小/速度 大、慢 小 3–10 倍、快 10–100 倍
自我描述 有(含 metadata) 沒有(靠 .proto 對照)
適合 開放、跨組織互通 相對封閉、追求效能的內部系統

書中提醒:拿 protobuf 跟 XML 比其實不太公平——Google 是相對封閉的系統,不需要 XML 那種跨開放系統的互通性與自我描述能力,所以可以選又小又快的方案。

💡
關鍵

XML 像自我描述的精美禮盒(互通好但笨重),protobuf 像靠編號對照表的樸素紙箱(小而快,但需雙方共享 .proto)。

STEP 3

實用超能力

用 protobuf 撐起一進一出的 RPC

從『描述資料』到『遠端呼叫』

protocol buffers 最常見的用途是定義 RPC(遠端程序呼叫)。只要多寫一段語法:

service SearchService {
  rpc Search (RequestType) returns (ResponseType);
}

protoc 會產生一個抽象介面與一個 stub(樁),讓你能型別安全地呼叫遠端服務。

兩個關鍵設計決策(都呼應簡單原則):

  1. 一進一出:每個遠端操作只能吃一個參數、回傳一個結果(兩者都是 protobuf 訊息)。這跟一般 RPC/RMI 可以有任意多參數很不一樣。
    • 為什麼? 為了可演進性。把複雜度推到『資料』裡,介面本身保持穩定不易變——這跟 REST 哲學(少量操作、聚焦於操作資源)相呼應。
  2. 與 RPC 協定無關 (protocol-agnostic):stub 只假設存在 RpcChannelRpcController 兩個抽象介面。你想用 HTTP、TCP 或第三方 RPC 實作都行,自己提供實作即可。
flowchart TD
  A[.proto 檔 定義 service 與 message] --> B[protoc 編譯]
  B --> C[產生 stub 與抽象介面]
  C --> D[呼叫端透過 stub 發出 RPC]
  D --> E[底層可換成 HTTP 或 TCP]
💡
關鍵

protobuf 加上 service 語法即可定義 RPC;採『一進一出』利於演進、且與底層 RPC 協定無關,可自由替換 HTTP/TCP。

🔆
生活妙喻:XML vs protocol buffers ≈ 印滿說明的精美禮盒 vs 只有編號標籤的樸素紙箱

XML 自我描述但笨重;protobuf 靠雙方共享的 .proto 對照表,又小又快。

🔆
生活妙喻:.proto 檔與 protoc 編譯 ≈ 設計圖與依圖自動生產的工廠

你只寫一份設計圖(.proto),工廠(protoc)就自動生產出存取資料的零件(程式碼)。

🔆
生活妙喻:一進一出的 RPC ≈ 餐廳只收一張點餐單、回一個餐盤

把所有需求塞進一張單子(一個訊息),介面就穩定,菜單改版也不必換窗口。

本節字彙

Serialization(序列化)
把記憶體中的結構化資料攤平成可傳輸或儲存的位元組序列,對方再還原。
🧠 Serial=排成一列,把立體的資料壓成一條長龍。
Protocol buffers
Google 的語言/平台中立序列化格式,用 .proto 描述、protoc 編譯,比 XML 小且快。
🧠 protobuf=協定用的緊湊『緩衝包裹』。
Stub(樁)
由 protoc 產生的本地代理,讓你像呼叫本地函式一樣呼叫遠端服務。
🧠 像插座(stub):本地插上去,背後接到遠端。
protocol buffers 沒有像 XML 那樣『自我描述』,卻仍能正確解讀資料。原因是什麼?
protobuf 的 RPC 採『每個操作只吃一個參數、回傳一個結果』。書中說這主要換來什麼好處?
在 .proto 中把某欄位標為 `repeated` 代表什麼?

發布訂閱:事件即時廣播

為什麼廣告系統不能用 RPC?topic-based 發布訂閱透過 broker 樹狀疊加網路,提供時間與空間解耦、可靠又即時的事件傳遞。

STEP 1

深度探秘

當 RPC 不夠用的時候

一對多的即時廣播,RPC 撐不住

protocol buffers 的 RPC 很適合『一對一、請求-回應』。但有些場景完全不適合 RPC,例如 Google 廣告系統

  • 廣告伺服器遍布全球,任何一台都得在幾分之一秒內知道某則廣告現在能不能投放。
  • 如果用 RPC:發送端得知道所有廣告伺服器的身分、對每一台發 RPC、佔用大量連線與緩衝區、還要耗掉廣域網路頻寬。根本不可行。

所以 Google 另外提供一個 publish-subscribe(發布訂閱) 系統來補強(不是取代)protobuf。它的關鍵特性是時間與空間解耦 (time and space uncoupling)

  • 空間解耦:發送者不需知道接收者是誰、在哪。
  • 時間解耦:發送與接收不必同時在線。

這也天然支援訂閱者的故障與復原。順帶一提,這個發布訂閱系統底層仍然用 protobuf 來通訊。

flowchart TD
  P[發布者 publisher 根節點] --> B1[broker]
  P --> B2[broker]
  B1 --> S1[訂閱者]
  B1 --> S2[訂閱者]
  B2 --> S3[訂閱者]
💡
關鍵

一對多的即時事件廣播用 RPC 不可行;發布訂閱靠時間與空間解耦補強 protobuf,發送者不必知道接收者是誰。

STEP 2

生活妙喻

廣播電台 vs 一通通打電話

你會打電話通知每個聽眾嗎?

假設你要通知全國上百萬人『颱風來了』:

  • RPC 方式:你拿著電話簿,一個一個打電話。你得知道每個人的號碼、一通通打、佔線又耗時。一百萬人?打到天荒地老。
  • 發布訂閱方式:你只要在廣播電台講一次。誰想聽就訂閱那個頻道。你不必知道聽眾是誰、有幾個、在哪裡——這就是空間解耦。聽眾晚一點打開收音機也能收到重播——這就是時間解耦

Google 採用 topic-based(主題式) 發布訂閱:每個頻道 (channel) 對應一個主題 (topic)

方式 優點 缺點
Topic-based(Google 採用) 容易實作、效能可預測 表達力較弱(只能選頻道)
Content-based 表達力強(可依內容細選) 較難實作、效能較難預測

為了補救表達力的不足,Google 允許加強版訂閱:每個事件含 header、一組 keywords 和一個不透明的 payload;訂閱時除了選頻道,還能用 filter(依關鍵字過濾) 挑出頻道內的子集。

💡
關鍵

發布訂閱像廣播電台(空間/時間解耦);Google 用易實作、效能可預測的 topic-based,再用關鍵字 filter 補救表達力。

STEP 3

實用超能力

用 broker 樹做到又可靠又即時

一棵棵『主題樹』如何運作

Google 的發布訂閱系統實作成一個 broker overlay(中介者疊加網路),長得像一組樹

  • 每棵樹代表一個主題
  • 樹的根 (root) 是發布者,葉節點 (leaf) 是訂閱者。
  • filter 會被盡量往樹的後方(靠近根)推,這樣不必要的流量就能及早被擋掉,省頻寬。

和第 6 章一般的發布訂閱不同,Google 特別強調『可靠』又『即時』:

  1. 可靠 (reliability):每個邏輯頻道維護兩棵獨立的樹(冗餘樹)。一棵出問題,另一棵頂上。
  2. 即時 (timely delivery):用 QoS(服務品質)管理 控制訊息流量——採用簡單的速率限制 (rate control),以『每使用者/每主題』為基礎,管理記憶體、CPU 與訊息/位元組速率的預期用量。

樹一開始用最短路徑演算法建構,並持續重新評估調整。

重點:頻道適合『相對靜態、粗粒度、高吞吐』的資料流(至少 1 Mbps)。若某主題流量不到這個量,會被併進別的主題,但仍可用關鍵字辨識出來。

💡
關鍵

用一棵棵主題樹(根為發布者、葉為訂閱者)構成 broker 疊加網路;靠冗餘樹保證可靠、靠速率控制保證即時,filter 盡量前推省頻寬。

🔆
生活妙喻:發布訂閱的時間/空間解耦 ≈ 廣播電台

主播不必知道聽眾是誰(空間解耦),聽眾晚點開收音機也能收到(時間解耦)。

🔆
生活妙喻:topic-based 頻道 ≈ 收音機的不同頻道

想聽哪個主題就轉到那個頻道訂閱,簡單、效能可預測,但只能整個頻道地選。

🔆
生活妙喻:冗餘樹 ≈ 重要訊息走兩條不同的郵路寄出

一條郵路出狀況,另一條仍能把信送達,確保可靠。

本節字彙

Publish-subscribe(發布訂閱)
發布者把事件丟到頻道,訂閱者訂閱感興趣的頻道接收,雙方互不需知道對方身分。
🧠 出版社出刊(publish)、讀者訂閱(subscribe),彼此不必認識。
Time/space uncoupling(時間/空間解耦)
發送與接收者不必同時在線(時間),也不必知道彼此身分(空間)。
🧠 解開兩條繩子:不必同時、不必相識。
Broker overlay(中介者疊加網路)
由中介節點組成、架在底層網路之上的轉發結構;Google 用一組樹來實作,每棵樹一個主題。
🧠 Broker=中間人,疊加在實體網路之上幫忙轉發。
為什麼 Google 廣告系統用 RPC 來把『廣告可投放性』通知到全球伺服器是不可行的?
發布訂閱的『空間解耦 (space uncoupling)』指的是什麼?
Google 選用 topic-based 而非 content-based 發布訂閱,主要的取捨是什麼?
04

資料儲存與協調服務

64MB 的大區塊、單一 master 管 metadata、控制流與資料流分離、預設三份複本,以及放寬的一致性模型。

GFS:為超大檔案打造的檔案系統

64MB 的大區塊、單一 master 管 metadata、控制流與資料流分離、預設三份複本,以及放寬的一致性模型。

STEP 1

深度探秘

不是萬用檔案系統,而是『為 Google 量身訂做』

GFS 的需求與一般檔案系統不一樣

Google File System (GFS) 也是分散式檔案系統,但它和 NFS、AFS 那種『萬用』檔案系統的目標完全不同。Google 觀察自己的使用模式後,提出三個特別的需求:

  1. 要能在會壞的便宜硬體上可靠運行:設計一開始就假設『元件(含軟硬體)必然會壞』。
  2. 檔案少但超大、存取以順序為主:約一百萬個檔案、平均 100 MB,有些到 GB 級。存取以大檔案的順序讀附加寫 (append) 為主,小型隨機讀寫很少。高度並行的 append 特別常見。
  3. 要滿足整體需求:可擴展、可靠、高效能、開放。效能上優化高且持續的讀取吞吐量,優先於延遲
flowchart TD
  C[GFS client] -->|問 metadata| M[GFS master 單一]
  M -->|回傳 chunk 位置| C
  C -->|直接讀寫資料| CS1[chunkserver 複本1]
  C --> CS2[chunkserver 複本2]
  C --> CS3[chunkserver 複本3]
💡
關鍵

GFS 不是萬用檔案系統,而是針對『超大檔案、順序讀與 append、硬體會壞』量身訂做,效能上優先吞吐量而非延遲。

STEP 2

生活妙喻

圖書館的大書庫與一位總館長

把 GFS 想成一座巨型書庫

  • 大 chunk(64 MB):GFS 把檔案切成固定 64 MB 的大區塊,比一般檔案系統大得多。就像書庫用大箱子收書,而不是一本一本散放。
    • 好處:適合大檔案、順序讀寫超有效率、需要的『目錄資訊 (metadata)』也少很多(若用 64 KB 的小區塊,metadata 會暴增 1000 倍)。
    • 代價:對『只想讀小檔案一小段』的隨機存取非常沒效率。
  • 單一 master(總館長):每個 GFS 叢集只有一位 master,負責管理 metadata(命名空間、存取控制、檔案→chunk 的對應、複本位置)。
    • 好處:館長有全域視野,能做最佳的放置決策;實作也簡單,開發快。
    • 代價:單點故障。怎麼補救? 把 master 的操作日誌 (operation log) 複製到多台機器,掛了能迅速重建。
  • chunkserver(書架管理員)+ 三份複本:每個 chunk 預設複製到三台獨立的 chunkserver,這樣壞一台也不怕(NFS/AFS 並不做這種更新複製)。

最妙的設計:控制流與資料流分離。 client 先問 master『這段資料在哪個 chunk、哪些複本上?』(控制流),拿到後把 master 晾在一邊,直接找 chunkserver 讀大量資料(資料流)。一次問完,之後 64 MB 想讀多快就讀多快。

💡
關鍵

64 MB 大 chunk 利於順序讀寫、減少 metadata;單一 master 管 metadata(用複製日誌補救單點故障);控制流與資料流分離讓讀寫高效。

STEP 3

實用超能力

放寬的一致性:write 與 record append 的差別

不追求完美一致,而是『夠用就好』

因為 chunk 有複本,更新時就得維持一致。GFS 用一種放寬的一致性 (relaxed consistency):當有 mutation(write/append/delete)時,master 把 chunk lease(租約) 給其中一個複本當 primary(主),由它決定並行更新的順序,再讓 secondary(從)依同樣順序套用。

這其實是被動複製 (passive replication) 的變形:一般被動複製中,資料和控制都走 primary;GFS 則讓 client 把資料直接送給所有複本,但控制請求才送 primary,由 primary 排序。這讓大量資料傳輸能獨立於控制流去優化。

write 與 record append 的關鍵差別:

write record append
位置 指定 offset 由系統決定(接在檔尾)
並行同位置寫 不可序列化,可能損毀
保證 至少一次 (at least once) 且原子,但不保證所有複本完全相同(可能有重複資料)

這是領域特定的取捨:GFS 明知道放寬一致性會讓資料可能重複,但因為 Google 的應用能容忍這種語意,就換取了更高的效能。

其他務實設計:GFS 幾乎不做快取(順序串流讀,快取幫助不大,還能省去 cache 一致性協定的麻煩);伺服器端則靠 Linux 的 buffer cache。所有 GFS 伺服器都維護大量診斷日誌,持續監控、出事時用來找根因。

💡
關鍵

GFS 用放寬一致性(primary 排序、client 直送資料給複本);record append 保證至少一次且原子但允許複本不完全相同,是能被 Google 應用容忍的領域特定取捨。

🔆
生活妙喻:64 MB 大 chunk ≈ 書庫用大箱子收書而非一本本散放

大箱子適合整批搬運(順序讀寫),需要的清單(metadata)也少;但要單獨抽一本小書(隨機小存取)就很不方便。

🔆
生活妙喻:單一 master 的全域視野 ≈ 一位掌握全館的總館長

一個人就掌握所有藏書位置,調度最佳化最簡單;缺點是館長一倒就麻煩,所以要備份他的工作日誌。

🔆
生活妙喻:控制流與資料流分離 ≈ 先向櫃台問書在哪,之後直接去書架搬

只問一次位置(控制流),之後直接大量搬書(資料流),不必每搬一本都回櫃台。

本節字彙

Chunk(區塊)
GFS 把檔案切成的固定大小單位,預設 64 MB,是儲存與複製的基本單位。
🧠 Chunk=一大塊,刻意切得很大。
Master / chunkserver
master 管 metadata(一台,有全域視野);chunkserver 實際存放 chunk 資料(很多台)。
🧠 master 是總館長管目錄,chunkserver 是書架管實體。
Record append
把資料原子地附加到檔尾的特殊操作,保證至少一次成功,適合高並行附加。
🧠 Append=接龍,多人同時往後接,由系統決定落點。
GFS 選用 64 MB 這麼大的 chunk,下列哪一項是它的『代價』而非好處?
GFS 的單一 master 是潛在的單點故障。GFS 用什麼方式來緩解這個風險?
GFS『控制流與資料流分離』的主要好處是什麼?

Chubby 與 Paxos:協調與共識

把鎖和檔案合而為一的 Chubby,如何用來選主、命名、存小資料;以及它核心的 Paxos 共識演算法三步驟。

STEP 1

深度探秘

一個服務,四種面貌

Chubby:把『鎖』和『檔案』合而為一

Chubby 是 Google 基礎設施的核心協調服務,被 GFS、Bigtable 等服務倚賴。它身兼四種能力

  1. 粗粒度分散式鎖 (coarse-grained distributed locks):在大規模非同步環境中同步各方活動。
  2. 小檔案儲存:可靠地存放少量資料(補足 GFS 不擅長的小檔案)。
  3. 協助選主 (primary election)
  4. 命名服務 (name service)

一個服務做四件事,看似違反『簡單原則』?其實它的核心只有一個:解決分散式共識 (distributed consensus),其餘三種面貌都從這個核心衍生而來。

介面像檔案系統(採 Plan 9『一切皆檔案』的觀點):路徑形如 /ls/chubby_cell/directory_name/.../file_name。實體(entity)同時具備檔案的功能——可純粹拿來鎖、可存小資料、也可把小資料(當 metadata)和鎖綁在一起。

操作刻意精簡:Open/Close/Delete、整檔讀寫的 GetContentsAndStat/SetContents整檔讀寫且原子,刻意不鼓勵存大檔),以及鎖操作 Acquire/TryAcquire/Release

💡
關鍵

Chubby 表面有鎖、小檔案、選主、命名四種能力,但核心其實只有一個——分散式共識,其餘都由此衍生。

STEP 2

生活妙喻

公司公佈欄上的『會議室預約單』

用一張預約單,順便公告誰是負責人

想像公司公佈欄上有一張會議室預約單。它同時做了兩件事:

  • :誰先簽上去,誰就鎖定這間會議室(拿到鎖)。
  • 檔案:簽名旁邊還能寫一行字(存小資料)。

這正是 Chubby 選主 (primary election) 的做法:

  1. 所有候選者都去搶同一個鎖
  2. 只有一個會成功——它就是 primary,其餘變 secondary;
  3. 勝出者把自己的身分寫進那個檔案,其他人讀檔案就知道誰是主。

這完美示範了『鎖+檔案合一』的妙用,也說明選主可以建在共識服務之上,作為環狀演算法或 bully 演算法的替代方案。

Chubby 的鎖是 advisory(建議性)的:系統不強制擋住沒拿鎖的人,大家得自律地走『先拿鎖再存取』的流程。為什麼不用更強硬的 mandatory(強制性)鎖?因為 advisory 鎖更有彈性、更有韌性

快取一致性:Chubby 的小檔案常被重複讀,所以 client 快取很有效(和 GFS 相反)。它用一個簡單的協定:要 mutation 時,先等所有相關快取失效才放行(失效通知搭便車附在 KeepAlive 回覆上),快取資料從不直接更新。結果是確定性 (deterministic) 的語意——這也是為什麼 Google 拿 Chubby 當命名服務(比 DNS 那種會不一致的更可靠)。

💡
關鍵

選主=大家搶同一個鎖、勝者把身分寫進檔案;Chubby 用 advisory 鎖與『先失效快取再放行』的簡單協定,提供確定性語意。

STEP 3

實用超能力

Paxos:在混亂中達成共識的三步驟

Chubby 的心臟:Paxos 共識演算法

一個 Chubby cell(實例) 通常有五個複本,其中一個是 master。複本資料庫的一致性靠 Paxos 共識演算法維持。Paxos 是為非同步系統設計的共識協定,假設:複本可任意速度運作也可能失敗復原、有持久儲存、訊息可能遺失/重排/重複但不會損毀。

關鍵特性:Paxos 保證正確性 (safety) 但不保證終止 (liveness)。也就是說它不會給出錯誤答案,但在純非同步下不保證一定談成(這與 Fischer 等人的不可能性結果一致)。只要多數複本穩定運作夠久,最終就能達成共識。

三步驟(用 Google 的術語):

sequenceDiagram
  participant C as 協調者 coordinator
  participant R as 複本們 replicas
  C->>R: 步驟1 Propose 帶序號
  R-->>C: Promise 承諾不理更舊的協調者
  C->>R: 步驟2 Accept 帶選定的值
  R-->>C: Acknowledgement
  C->>R: 步驟3 Commit 通知達成共識
  • 步驟 1(選協調者):想當協調者的複本挑一個更高的唯一序號廣播 Propose。序號唯一性靠:每個複本有唯一 id $i_r$($0$ 到 $n-1$),挑最小的、比看過的都大、且 $s \bmod n = i_r$ 的序號(例如 5 個複本、id=3、最後看到 20,就挑 23)。收到多數 Promise 就當選,這群多數叫 quorum(法定人數)
  • 步驟 2(求共識):協調者選一個值送 Accept。若有 Promise 帶回舊的值,就必須沿用其中一個;否則可自選。等多數 Acknowledge。
  • 步驟 3(達成共識):多數確認後即達成,廣播 Commit。

為什麼安全? 靠 quorum:任兩個多數至少有一個共同複本,且網路分裂時最多只有一個分區能湊出多數。實務上 Chubby 要對『一連串的值』達成共識(log 的每一格),稱為 Multi-Paxos,可長期固定一個協調者以省掉重複的步驟 1。

💡
關鍵

Paxos 用 Propose-Accept-Commit 三步驟在非同步環境達成共識,靠 quorum 保證安全;它保證正確但不保證終止,實務用 Multi-Paxos 對一連串值達成共識。

🔆
生活妙喻:Chubby 鎖+檔案合一 ≈ 公佈欄上的會議室預約單

簽名=拿鎖,旁邊寫字=存小資料;選主就是大家搶簽同一張單,勝者把身分寫上去。

🔆
生活妙喻:Paxos 的 quorum(法定人數) ≈ 開會要過半才算數

任兩次『過半同意』必有至少一人重疊,確保不會出現兩個互相矛盾的決議。

🔆
生活妙喻:保證正確但不保證終止 ≈ 謹慎的裁判寧可不判,也絕不誤判

Paxos 在混亂時可能遲遲談不成(不終止),但絕不會給出錯誤共識(保證正確)。

本節字彙

Distributed consensus(分散式共識)
讓多個複本對某個值達成一致的問題;Chubby 的核心就是解這個問題。
🧠 Consensus=大家點頭達成一致。
Advisory lock(建議性鎖)
系統不強制阻擋未持鎖者,使用者須自律遵守『先取鎖再存取』;比強制鎖更有彈性。
🧠 Advisory=建議,靠大家配合而非硬擋。
Quorum(法定人數)
Paxos 中支持協調者的多數複本集合;任兩個多數必有交集,保證一致性。
🧠 過半才算數,兩次過半必有重疊。
Chubby 同時提供鎖、小檔案、選主、命名四種能力,書中說這並未違反『簡單原則』。為什麼?
用 Chubby 實作 primary election 的核心做法是什麼?
關於 Paxos 的特性,下列何者正確?

Bigtable:結構化資料的分散式儲存

由 row key、column family:qualifier、timestamp 構成的三維表格;tablet、SSTable、memtable 的儲存架構,以及用 Chubby 監控的巧思。

STEP 1

深度探秘

不是關聯式資料庫,而是超大表格

為什麼不直接用關聯式資料庫?

GFS 很會存『大平檔』,但很多 Google 應用(搜尋、Maps、Analytics、個人化搜尋)需要結構化、可依內容索引的資料。Google Analytics 例如把原始點擊存一張表(約 200 TB),分析後摘要存另一張(約 20 TB)。

大可用關聯式資料庫(支援 union、selection、join 等完整運算)。但在分散式環境要做到好效能與可擴展性非常難,而且 Google 的應用根本不需要這麼完整的功能。於是 Google 打造了 Bigtable:保留『表格』模型,但介面簡單得多,專為高效存取超大結構化資料集設計。

Bigtable 是一個三維表格,每個 cell 由三個鍵索引:

  • Row(列):row key 是任意字串(最多 64 KB)。表格依 row key 字典序排序
  • Column(欄):欄被組織成 column family(欄族),用 family:qualifier 命名。通常欄族少、但每個欄族內的欄(qualifier)可以很多。
  • Timestamp(時間戳):同一個 cell 可有多個版本,依時間戳索引,反向排序(最新的先出)。可設定自動垃圾回收舊版本。
💡
關鍵

Bigtable 保留表格模型但刻意比關聯式資料庫簡單,是由 row key、column family:qualifier、timestamp 構成的三維表格,專為超大結構化資料而設計。

STEP 2

生活妙喻

一本可以查歷史版本的超級電話簿

三維表格像什麼?

想像一本超級電話簿:

  • 列 (row)=每一個人(用 row key 找);
  • 欄族:欄 (family:qualifier)=資訊分類,例如『聯絡方式:手機』『聯絡方式:市話』『地址:住家』;
  • 時間戳 (timestamp)=這筆資料的歷史版本,最新的擺最前面。

字典序排序帶來的超能力:locality(區域性)。 Bigtable 依 row key 字典序排,連續的列會落在同一個 tablet(小表) 上。所以 row key 怎麼取很關鍵

row key 取法 排序結果
www.bbc.co.uk/sportwww.bbc.co.uk/football 被早期欄位主導,散得亂七八糟
uk.co.bbc.www/sportuk.co.bbc.www/football反轉網域 同網域被排在一起,落在同一 tablet,利於分析

把網域反轉當 row key,相關資料就會聚在一起——這個小技巧大幅提升資料區域性。注意:取 key 完全由程式設計師負責,要懂這個排序特性才能用得好。

所有對單一列的存取都是原子的(呼應 GFS、Chubby 的設計)。但 Bigtable 不支援跨列的全域交易——又一次『簡單但夠用』的取捨。

💡
關鍵

Bigtable 像可查歷史版本的電話簿;依 row key 字典序排序,把網域反轉當 key 可讓相關資料聚在同一 tablet(提升區域性),單列存取原子但不支援跨列交易。

STEP 3

實用超能力

站在 GFS 與 Chubby 的肩膀上

Bigtable 的架構:和 GFS 像得驚人

Bigtable 把表格切成 tablet(約 100–200 MB)。一個 Bigtable 實例叫 cluster,架構有三部分:client 端 library單一 master server大量 tablet server

借用了 GFS 的兩個關鍵決策

  1. 單一 master(全域視野、好實作);
  2. 控制與資料流分離(master 只做控制:監控 tablet server、分派 tablet、負載平衡、垃圾回收;資料存取完全走 tablet server)。

Bigtable 甚至比 GFS 更進一步:master 不參與『tablet → 實際資料』的對應,所以 client 完全不必跟 master 通訊,大幅減輕 master 負擔。

資料怎麼存?SSTable(有序、不可變的 key→value 對應檔,存在 GFS)。寫入先進持久 log(也在 GFS),再寫到記憶體裡的 memtable;讀取則合併 SSTable 與 memtable 的視圖。tablet 的定位用一個受 B+-tree 啟發的三層階層索引

flowchart TD
  A[Chubby 檔案 指向根] --> B[Root tablet]
  B --> C[其他 metadata tablets]
  C --> D[使用者資料 tablets]

最妙的:用 Chubby 監控 tablet server。 每台 tablet server 上線時在 Chubby 目錄建一個檔並取得獨佔鎖。鎖在=健在。

  • server 端:發現鎖丟失就停止服務;檔案還在就試著重拿鎖,檔案消失就自我了斷。
  • master 端:定期查鎖狀態;若鎖丟失或 server 沒回應,master 嘗試搶鎖,搶到代表 Chubby 活著、問題在 server,於是刪掉該檔(逼 server 自盡)並把它的 tablet 重新分派。

這是重用一個可靠服務(Chubby)來達成監控,而不是另造一個監控服務——再次呼應簡單原則。

💡
關鍵

Bigtable 借用 GFS 的單一 master 與控制/資料流分離,並比 GFS 更進一步讓 client 不必碰 master;資料以 SSTable 存於 GFS,並巧妙重用 Chubby 的鎖來監控 tablet server。

🔆
生活妙喻:三維表格(列/欄族/時間戳) ≈ 可查歷史版本的超級電話簿

每人一列、資訊分類成欄族:欄、每筆資料還留有依時間排序的舊版本。

🔆
生活妙喻:反轉網域當 row key ≈ 圖書館把書按『主題再書名』而非『書名』上架

讓相關的書(同網域資料)排在一起,找一整類時不用跑遍全館。

🔆
生活妙喻:重用 Chubby 鎖做監控 ≈ 用既有的門禁刷卡紀錄判斷員工在不在

不另設打卡機,直接看門禁鎖的狀態就知道 tablet server 是否健在。

本節字彙

Column family(欄族)
Bigtable 中對欄的邏輯分組,用 family:qualifier 命名,同族資料型別相近。
🧠 Family=一家人,同類欄住在同一族裡。
Tablet
Bigtable 把表格依列範圍切成的小塊(約 100–200 MB),是分散與放置的單位。
🧠 Tablet=小藥片,大表格切成可分散的小片。
SSTable
有序、不可變的 key→value 對應檔,是 Bigtable 主要儲存單位,存放於 GFS。
🧠 Sorted String Table:排好序、不可改的字串表。
Google 明明可以用功能完整的關聯式資料庫,為什麼還要另造 Bigtable?
把 BBC 體育資料的 row key 從 `www.bbc.co.uk/sport` 改成 `uk.co.bbc.www/sport`(反轉網域),主要好處是?
Bigtable 在哪一點上比 GFS『更進一步』地減輕 master 負擔?
05

分散式運算服務

map 產生中間鍵值對、reduce 彙整結果;框架自動處理切片、排程、容錯與落後者;以及為何它能把程式碼大幅縮減。

MapReduce:兩個函式征服海量運算

map 產生中間鍵值對、reduce 彙整結果;框架自動處理切片、排程、容錯與落後者;以及為何它能把程式碼大幅縮減。

STEP 1

深度探秘

把『平行運算』濃縮成兩個函式

一個驚人的觀察

有了海量資料後,怎麼算?Google 觀察到許多平行運算其實長得一樣

  1. 把輸入資料切成小塊;
  2. 對每塊做初步處理,產生中間結果;
  3. 把中間結果合併成最終輸出。

既然模式相同,那就把『會變的部分』抽成兩個函式,其餘『不會變的部分』(平行化、排程、容錯、負載平衡…)由框架統一處理。這就是 MapReduce

  • map:吃一組 key-value 對,吐出一組中間 key-value 對。
  • (中間步驟):框架把中間結果依 key 排序、分組
  • reduce:吃同一個 key 的那組值,產生最終結果(可能是一個值或一串值)。
flowchart TD
  A[輸入資料] --> B[切成多塊]
  B --> C[多個 map 產生中間 key-value]
  C --> D[依 key 排序分組]
  D --> E[多個 reduce 彙整]
  E --> F[輸出結果]

威力有多大? 2003 年 Google 用 MapReduce 重寫主要索引系統,C++ 行數從 3,800 降到 700。而且改善底層 MapReduce,所有應用立刻受惠

💡
關鍵

MapReduce 把『相同的平行運算骨架』抽出來,開發者只寫 map 與 reduce 兩個函式,平行化、排程、容錯都交給框架。

STEP 2

生活妙喻

全班一起數一大箱選票

怎麼又快又準地數完一大箱選票?

一箱幾百萬張選票,一個人數要數到天荒地老。聰明的做法:

  1. 分堆 (map):把選票分給很多同學,每人拿一疊。每位同學翻看自己那疊,每看到一張票就喊出『候選人 A,+1』『候選人 B,+1』——這就是 map 吐出 <候選人, 1> 的中間結果
  2. 歸類 (中間步驟):把所有『候選人 A』的紙條收到一起、『候選人 B』的收到一起——這就是依 key 排序分組
  3. 加總 (reduce):每個候選人指派一個同學,把屬於他的那堆 1 全部加起來——這就是 reduce

這正是經典的 word count(字數統計) 範例:map 對每個出現的字 emit <word, 1>,reduce 把同一個字的所有 1 加總。

應用 map 做什麼 reduce 做什麼
Word count 每個字 emit <字, 1> 加總同字的 1
Grep 符合樣式就輸出該行 (幾乎不用)
倒排索引 emit <字, 文件ID> 為每個字產生排序後的文件 ID 清單

你只要負責『怎麼分堆、怎麼加總』,至於『派給誰、誰偷懶、誰中途離場』全由 MapReduce 框架搞定。

💡
關鍵

MapReduce 就像全班分工數選票:map 分堆並各自計數、框架歸類、reduce 加總;經典範例是 word count。

STEP 3

實用超能力

框架如何容錯、又如何對付『拖油瓶』

執行細節與容錯的真功夫

執行流程:輸入切成 M 塊(每塊 16–64 MB,不大於一個 GFS chunk),中間結果的鍵空間用 partition function 切成 R 塊(預設 hash(key) mod R)。框架啟動一批 worker,其中一個當 master,其餘做 map 或 reduce。典型規模:M=200,000、R=5,000、2,000 台 worker。

資料區域性 (locality):輸入存在 GFS 且複製三份,master 盡量把 map worker 安排在存有該資料的機器上,避免耗網路頻寬——把運算搬到資料旁邊

容錯:master 定期 ping 每個 worker,沒回應就當它掛了:

  • 若是 map 任務:不管做完沒,都標記為 idle 重排(因為結果存在本機磁碟,機器掛了就拿不到)。
  • 若是 reduce 任務:只有還在進行中才重排(已完成的結果寫到全域複製的檔案系統,仍可取得)。

只要 map/reduce 對輸入是確定性 (deterministic) 的,即使有故障,MapReduce 也保證產出和循序執行相同的結果(關鍵在於輸出是原子寫入的)。

對付 straggler(落後者/拖油瓶):有些 worker 因硬碟出毛病等原因跑很慢。當程式接近完成時,master 會為剩下的進行中任務啟動備援 worker,誰先完成就算誰的。這對縮短完成時間影響顯著。

MapReduce 常與 Bigtable 搭配:從 Bigtable 讀入、輸出又寫回 Bigtable(如 Google Analytics、Google Maps 的圖磚生成)。

💡
關鍵

MapReduce 靠資料區域性省頻寬、靠重排與備援 worker 容錯與對付 straggler,並保證在確定性函式下產出與循序執行相同的結果。

🔆
生活妙喻:map 與 reduce ≈ 全班分工數選票:先各自分堆計數,再依候選人加總

map 是每人翻自己那疊喊出 <候選人, 1>,reduce 是把同一候選人的所有 1 加起來。

🔆
生活妙喻:資料區域性 (locality) ≈ 在食材所在的廚房就地烹調,而不是把食材千里運來

把運算搬到資料所在的機器,避免在網路上搬運大量資料。

🔆
生活妙喻:備援 worker 對付 straggler ≈ 接力賽尾聲派第二棒同時起跑,誰先到算誰的

對跑很慢的任務另開一個備援,先完成的就採用,避免被一個拖油瓶拖垮。

本節字彙

MapReduce
用 map 與 reduce 兩個函式描述平行運算的程式模型,框架負責平行化、排程與容錯。
🧠 Map=分而處理,Reduce=合而彙整。
Locality(資料區域性)
盡量把運算安排在存有所需資料的機器上,減少網路傳輸。
🧠 就近取材,別把資料搬來搬去。
Straggler(落後者)
執行得異常緩慢的 worker;MapReduce 用備援 worker 來避免被它拖垮。
🧠 拖油瓶:跑太慢的那個,另派人頂上。
MapReduce 最核心的設計洞見是什麼?
在 word count 範例中,map 與 reduce 各自負責什麼?
MapReduce 為何盡量把 map worker 安排在『存有該輸入資料的機器』上執行?

Sawzall 與整章設計智慧回顧

比 MapReduce 更高階的 Sawzall 語言(filter 與 aggregator),以及貫穿全章的核心啟示:理解領域、提煉原則、一致套用。

STEP 1

深度探秘

比 MapReduce 更高一層的語言

Sawzall:讓平行分析更好寫

MapReduce 已經很強,但寫起來仍有些繁瑣。Sawzall 是一個直譯式程式語言,目標是進一步簡化在超大資料集上的平行分析。實際成效驚人:Sawzall 程式常比等效的 MapReduce 程式小 10 到 20 倍

Sawzall 不是另起爐灶,而是站在現有基礎設施上:用 MapReduce 來建立與管理底層平行執行、用 GFS 存資料、用 protocol buffers 當共同的紀錄格式。

它假設平行運算遵循一個模式:輸入是一堆記錄 (records),由 filter(過濾器) 平行處理每一筆並 emit 結果,再由 aggregator(彙整器) 把 emit 的資料彙整成最終結果。

對應到 MapReduce:filter 跑在 map 階段,aggregator 對應 reduce 階段。

flowchart TD
  A[原始資料 records] --> F1[filter]
  A --> F2[filter]
  A --> F3[filter]
  F1 --> AG[aggregator 彙整]
  F2 --> AG
  F3 --> AG
  AG --> R[結果]
💡
關鍵

Sawzall 是建在 MapReduce/GFS/protobuf 之上的高階語言,用 filter 與 aggregator 描述運算,程式比 MapReduce 小 10–20 倍。

STEP 2

生活妙喻

工廠的『分檢員』與『統計員』

Sawzall 像一條自動化產線

想像一條輸送帶送來大量產品(records):

  • filter(分檢員):站在輸送帶旁,各自獨立檢查每一件產品,把符合條件的資料挑出來丟到籃子裡(emit)。很多分檢員可以同時、任意順序工作。
  • aggregator(統計員):把籃子裡的資料彙整出總結,例如加總(sum)、收集全部(collection)、算累積機率分布(quantile)、估最常見的值(top)。

Sawzall 對這兩種角色有兩個關鍵假設:

  1. filter 必須可交換 (commutative):分檢員的工作順序不影響結果(誰先檢查都一樣)。
  2. aggregator 必須可結合 (associative):彙整時的括號分組不影響結果(先合誰、後合誰都行)。

為什麼這兩個假設這麼重要?因為唯有可交換、可結合,才能放心地平行化——可以任意拆分、任意順序、任意分組去執行,最後結果都一樣。這就是平行運算能成立的數學前提。

一個迷你 Sawzall 範例(計數+加總一串浮點數):

count: table sum of int;
total: table sum of float;
x: float = input;
emit count <- 1;
emit total <- x;

table sum of ... 宣告了兩個『加總型』aggregator,emit 把值送進去——程式短得不可思議。

💡
關鍵

Sawzall 像產線上的分檢員(filter)與統計員(aggregator);filter 須可交換、aggregator 須可結合,這正是能安全平行化的數學前提。

STEP 3

實用超能力

回顧整章:真正的設計功力在哪

把整章的智慧串起來

這一整章其實在傳一個核心訊息:好的分散式系統設計,不在於用了多炫的技術,而在於『理解領域、提煉核心原則、然後一致地套用』。

反覆出現的設計模式(你應該已經很熟了):

模式 在哪裡出現
簡單但夠用 protobuf(不要 XML 的萬用)、Bigtable(不要完整關聯運算)、Chubby(只整檔讀寫)
單一 master + 全域視野 GFS、Bigtable
控制流與資料流分離 GFS、Bigtable
領域特定的取捨 GFS 放寬一致性、topic-based 發布訂閱
重用既有可靠服務 Bigtable 用 Chubby 監控、Sawzall 用 MapReduce
多種互補方案,各選最適 通訊(protobuf+發布訂閱)、儲存(GFS+Chubby+Bigtable)

MapReduce/Sawzall 的共同哲學:先鼓勵一種特定的運算風格,再提供共用基礎設施讓這種風格能高效實作。代價是框架較具規範性,未必適合所有問題領域——這也引發了資料管理社群關於『這類抽象是否足夠通用』的有趣辯論。

整本書的收尾啟示:Google 的成功,源自把『simplicity、every millisecond counts、嚴格測試與 logging/tracing』這套原則從上到下、始終如一地貫徹。技術會變,但這套設計紀律才是真正可帶走的功夫。

💡
關鍵

整章的核心啟示:理解領域、提煉核心原則並一致套用;簡單、單一 master、控制/資料分離、領域特定取捨、重用服務、多種互補方案是反覆出現的設計模式。

🔆
生活妙喻:filter 與 aggregator ≈ 產線上的分檢員與統計員

分檢員各自獨立挑出資料(filter),統計員把挑出的資料彙整成總結(aggregator)。

🔆
生活妙喻:可交換與可結合是平行化前提 ≈ 算一堆數字的總和,誰先加、怎麼分組加都一樣

因為加法可交換可結合,才能拆給很多人同時加、最後合併,結果保證相同。

🔆
生活妙喻:一致套用核心原則 ≈ 好建築師有一套貫穿全棟的設計語言

不是每層樓各搞各的,而是同一套原則從地基到屋頂始終如一,整體才協調。

本節字彙

Sawzall
建在 MapReduce 之上的高階直譯式語言,用 filter 與 aggregator 簡化大規模平行分析。
🧠 像一把好用的電鋸(Sawzall),快速切開大資料。
Commutative / Associative(可交換/可結合)
filter 須可交換(順序不影響結果)、aggregator 須可結合(分組不影響結果),是安全平行化的前提。
🧠 順序無所謂(交換)、括號無所謂(結合),才能放心平行。
領域特定取捨
依應用領域的實際需求刻意放寬或限縮功能(如 GFS 放寬一致性),以換取效能或可擴展性。
🧠 量身訂做:知道自己要什麼,就敢捨棄不需要的。
Sawzall 與 MapReduce 是什麼關係?
為什麼 Sawzall 要求『filter 可交換、aggregator 可結合』?
下列哪一項是貫穿整章、反覆出現的 Google 設計模式?