01

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

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

先讀原文段落,旁邊就是白話

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

原文 · 為什麼研究 Google:需求與挑戰 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 級儲存與容錯能力。

先讀原文段落,旁邊就是白話

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

原文 · 整體架構與設計哲學 As mentioned above, information about the locations of chunks is cached at clients when first accessed, to minimize interactions with the master. Apart from that, no other client caching is used. In particular, GFS clients do not cache file data. Given the fact that most accesses involve sequential streaming, for example reading through web content to produce the required inverted index, such caches would contribute little to the performance of the system.
白話導讀

為什麼 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 of acquiring and releasing locks. The developers of Chubby did consider an alternative of mandatory locks, whereby locked data is inaccessible to all other users and this is enforced by the system, but the extra flexibility and resilience 942 CHAPTER 21 DESIGNING DISTRIBUTED SYSTEMS: GOOGLE CASE STUDY of advisory locks was preferred, leaving the responsibility of checking for conflicts to the programmer [Burrows 2006]. Should an application need to protect a file from concurrent access, it can use both the roles together, storing data in the file and acquiring locks before accessing this data. Chubby can also be used to support a primary election in distributed systems – that is, the election of one replica as the primary in passive replication management (refer back to Sections 15.
白話導讀

用一個簡單語言描述資料格式,編譯成程式碼,比 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 (with its large, sequential reads and appends), client caching is effective in Chubby with its small files that are likely to be accessed repeatedly. Because of this caching, the system must maintain consistency between a file and a cache as well as between the different replicas of the file. The required cache consistency in Chubby is achieved as follows. Whenever a mutation is to occur, the associated operation (for example, SetContents) is blocked until all associated caches are invalidated (for efficiency, the invalidation requests are piggybacked onto KeepAlive replies from the master with the replies sent immediately when an invalidation occurs).
白話導讀

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, described in Section 21. ) One choice for Google would be to implement (or reuse) a distributed database, for example a relational database with a full set of relational operators provided (for example, union, selection, projection, intersection and join). But the achievement of good performance and scalability in such distributed databases is recognized as a difficult problem and, crucially, the styles of application offered by Google do not demand this full functionality. Google therefore has introduced Bigtable [Chang et al.
白話導讀

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 設計模式?