系統設計
把資料結構放大到好幾台機器
可搜尋中文、英文名稱和關鍵字。
主題
每個主題都是一個小模擬:調流量、調參數,看延遲、命中率和搬動的資料量怎麼變。數字是用固定亂數種子模擬出來的,所以每次看到的都一樣,也都可以重現。
架構圖庫
同一套元件,因為需求不同而排成不同的架構。點進去可以一步步走過請求的流程,圖上每個方塊都能點進對應的元件主題。
- 案例:短網址服務
把長網址換成短代碼:怎麼產生不重複的代碼、讀遠多於寫時怎麼擋住流量、資料多到要分片時怎麼切。
- 案例:動態牆
發文時推給所有追蹤者,還是讀取時才去拉?名人有幾百萬追蹤者時,兩種做法的成本完全相反。
- 案例:從一台機器到百萬用戶
使用者一路變多,每次都是某個瓶頸先爆:資料庫、單台伺服器、讀取量、寫入量。看每個元件是在哪個時間點、為了解決哪個瓶頸登場的。
- 案例:搜尋引擎
離線的爬蟲和建索引管線,加上線上的查詢服務:兩邊的需求完全不同,一邊要大吞吐量,一邊要毫秒級的延遲。
- 案例:即時通訊
訊息要即時送到對方的每一台裝置、對方離線時改用推播、而且順序不能亂:長連線閘道、訊息儲存和上線狀態怎麼配合。
- 案例:UGC 影音平台
UGC 是 User-Generated Content(使用者產生的內容)。使用者上傳的影片要轉成多種解析度,再透過 CDN(內容傳遞網路)播放出去:上傳和觀看兩條路徑的規模差了好幾個數量級。
- 案例:金流系統
錢不能多扣也不能少記:冪等鍵擋住重複付款、複式記帳讓每一筆帳都對得起來,和外部銀行對帳則抓出兩邊的不一致。
- 案例:叫車服務
幾十萬台車每幾秒回報一次位置,乘客叫車時要在毫秒內找到附近的空車:寫入量很大、資料很快就過期,所以放在記憶體裡的空間索引。
- 案例:限時搶購
幾十萬人同時搶一百件商品:不能超賣、也不能讓資料庫被打垮。用快取原子地扣庫存、用佇列把訂單的尖峰攤平。
- 案例:分散式鍵值儲存
Dynamo 式的鍵值儲存:一致性雜湊決定資料放在哪、Quorum 決定讀寫要幾台確認、gossip 傳遞誰還活著、向量時鐘處理衝突,叢集設定則由 Raft 管理。前面好幾個主題在這裡組成一個完整的系統。
- 案例:協同編輯
好幾個人同時編輯同一份文件,每個人都要即時看到別人的修改,最後還要完全一致:OT(Operational Transformation)和 CRDT(Conflict-free Replicated Data Type)兩種做法。
- 案例:檔案同步
像 Dropbox 一樣在多台裝置間同步檔案:切成區塊、只傳有變動的部分、相同的內容只存一份,發生衝突時保留兩個版本。
- 案例:通知系統
一次要送出幾百萬則推播、簡訊和 email:排隊、限流、重試、去除重複,還要尊重每個使用者的通知設定和勿擾時段。
- 案例:即時熱門排行
每秒幾十萬個事件,要隨時算出過去一小時最熱門的前 10 名:串流處理、滑動視窗,以及用 Count-Min Sketch 在固定記憶體裡估計次數。
流量
- 負載平衡核心
把請求分給好幾台伺服器。輪流分最簡單,但只要有一台比較慢,它就會成為整體延遲的瓶頸。
- 限流器核心
限制每個用戶每秒能打幾次。固定視窗、滑動視窗、Token bucket 三種做法,差別在邊界上和突發流量時。
- CDN核心
CDN 是 Content Delivery Network(內容傳遞網路):把內容複製到離使用者近的邊緣節點,大部分請求不用跨海回到原始伺服器,延遲和原站負載一起下降。
- 訊息佇列與事件串流核心
生產者把工作丟進佇列就走,消費者按自己的速度慢慢處理:尖峰流量被攤平,一邊掛了另一邊也不受影響。
也包含:事件驅動與串流處理
- API 閘道核心
API(Application Programming Interface,應用程式介面)前面的閘道,是所有外部請求的單一入口:驗證身分、限流、把請求路由到後面的服務,讓每個服務不必各自處理一遍。
- 輪詢、長輪詢與 WebSocket核心
伺服器要主動通知用戶端時,輪詢太浪費,改用 WebSocket 長連線:連線本身成了要管理的狀態,系統得知道每個使用者連在哪一台閘道上。
資料分散與一致
- 快取核心
在慢的資料庫前面放一層快的記憶體。因為熱門資料通常很集中,小小的快取就能擋掉大部分的讀取。
也包含:快取寫入策略、快取的典型問題:擊穿、穿透、雪崩
- 一致性雜湊核心
把資料分散到好幾台機器上,而且加減一台機器時只需要搬一小部分資料,不是全部重新洗牌。
- 資料庫複製核心
一台主資料庫負責寫,好幾台副本分擔讀。代價是副本會落後:剛寫進去的資料,從副本讀可能還看不到。
- 一致性與 Quorum進階
資料存 N 份時,寫入要幾份確認、讀取要問幾份?只要 W + R > N,讀到的就一定包含最新的寫入;網路斷開時,則要在一致和可用之間選一個。
- 分割與分片核心
垂直切分把欄位或功能拆到不同的表和資料庫,讓常用的資料更小、更容易進快取;水平分片則依某個鍵把列切到好幾台機器。切法決定了查詢要問幾台,也決定了會不會有一台特別忙。
- SQL 與 NoSQL核心
SQL(Structured Query Language,結構化查詢語言)代表關聯式資料庫,NoSQL(Not only SQL)泛指其他種類。關聯式資料庫給你交易和任意查詢;鍵值、文件、寬欄資料庫放棄其中一部分,換取容易水平擴充。該選哪個,取決於資料會怎麼被讀寫。
儲存引擎與索引
- LSM tree進階
LSM 是 Log-Structured Merge 的縮寫:寫入先進記憶體、攢滿再整批寫成排序好的檔案,背景再合併:寫入很快,讀取要多查幾個檔案,所以用布隆過濾器跳過不可能的檔案。
- 搜尋索引進階
倒排索引記錄每個詞出現在哪些文件:搜尋多個詞時,只要把幾串排好序的文件編號取交集,不用掃過每一篇文件。
- 物件儲存進階
把檔案當成一個個帶鍵的物件存放:容量幾乎無限、便宜又耐久,但不能只改檔案的一部分,也不適合拿來當資料庫查詢。
- Redis 與 Memcached核心
兩個最常見的記憶體快取:Memcached 只做簡單的鍵值、多執行緒;Redis 有豐富的資料型別、持久化和複製,淘汰用的則是抽樣近似的 LRU(Least Recently Used,最近最少使用)。
- 資料庫查詢執行計畫進階
同一句 SQL,資料庫可以用很多種方式執行:全表掃描還是走索引、巢狀迴圈、雜湊還是合併連接(JOIN)。執行計畫說明它實際選了哪一種、讀了多少資料,也解釋為什麼加一個索引能快上千倍,或完全沒用。
正確性與交易
協調與共識
- Raft 共識與領導者選舉進階
好幾台機器要對同一串操作達成共識,即使其中幾台當機:Raft 先選出一個領導者,每筆寫入都要由它複製到多數節點才算數;領導者掛了就重新選一個。
- 唯一 ID 產生進階
好幾台機器同時產生 ID,還不能重複:資料庫自動遞增、UUID(Universally Unique Identifier)、Snowflake(時間戳+機器編號+序號),差在能不能依時間排序、ID 有多長、需不需要彼此協調。
- 分散式鎖、租約與 fencing token進階
好幾台機器搶同一個資源時要一把鎖,但持鎖的那台可能暫停、斷線或時鐘跳掉:租約讓鎖會自動過期,fencing token 則讓舊的持鎖者回來時,它的寫入會被拒絕。
原則與取捨
- ACID 與交易隔離等級核心
資料庫交易的四個保證:原子性(Atomicity,全有或全無)、一致性(Consistency,維持約束)、隔離性(Isolation,互不干擾)、持久性(Durability,寫入不會丟)。隔離等級越低越快,但會出現髒讀、遺失更新、幻讀這些異常。
也包含:MVCC、鎖等待與死鎖
- CAP 定理核心
C、A、P 是一致性(Consistency)、可用性(Availability)、分區容錯(Partition tolerance)。網路分區一定會發生,發生的時候只能選一個:拒絕請求以保住一致性,或繼續服務但兩邊的資料可能分歧。常聽到的「三取二」,其實是這個取捨的簡化說法。
- 分散式系統的時間與順序進階
每台機器的時鐘都不準:用時間戳排序,回覆可能排到原訊息前面。Lamport 時鐘和向量時鐘不靠實際時間,只記錄「誰發生在誰之前」。
- 粗估與延遲數字核心
設計之前先算數量級:每秒幾個請求、要存多少資料、需要幾台機器。記住記憶體、SSD(Solid-State Drive,固態硬碟)、跨機房之間的延遲差了幾個數量級,就能很快看出哪個設計行不通。
- 容錯模式:逾時、重試、斷路器核心
下游一變慢,沒設逾時會讓執行緒全部卡住,盲目重試則會把它徹底壓垮:逾時、指數退避加隨機、斷路器、艙壁隔離,讓一個元件出事不會拖垮整個系統。
- 可觀測性:指標、日誌、追蹤進階
系統出事時要能回答「哪裡慢、為什麼」:指標看趨勢、日誌看細節、分散式追蹤看一個請求經過了哪些服務。再用 SLO(Service Level Objective,服務水準目標)和錯誤預算決定該先修問題還是上新功能。
案例
- 案例:短網址服務核心
把長網址換成短代碼:怎麼產生不重複的代碼、讀遠多於寫時怎麼擋住流量、資料多到要分片時怎麼切。
- 案例:動態牆核心
發文時推給所有追蹤者,還是讀取時才去拉?名人有幾百萬追蹤者時,兩種做法的成本完全相反。
- 案例:從一台機器到百萬用戶核心
使用者一路變多,每次都是某個瓶頸先爆:資料庫、單台伺服器、讀取量、寫入量。看每個元件是在哪個時間點、為了解決哪個瓶頸登場的。
也包含:垂直擴充與水平擴充
- 案例:搜尋引擎進階
離線的爬蟲和建索引管線,加上線上的查詢服務:兩邊的需求完全不同,一邊要大吞吐量,一邊要毫秒級的延遲。
- 案例:即時通訊核心
訊息要即時送到對方的每一台裝置、對方離線時改用推播、而且順序不能亂:長連線閘道、訊息儲存和上線狀態怎麼配合。
- 案例:UGC 影音平台進階
UGC 是 User-Generated Content(使用者產生的內容)。使用者上傳的影片要轉成多種解析度,再透過 CDN(內容傳遞網路)播放出去:上傳和觀看兩條路徑的規模差了好幾個數量級。
- 案例:金流系統進階
錢不能多扣也不能少記:冪等鍵擋住重複付款、複式記帳讓每一筆帳都對得起來,和外部銀行對帳則抓出兩邊的不一致。
- 案例:叫車服務進階
幾十萬台車每幾秒回報一次位置,乘客叫車時要在毫秒內找到附近的空車:寫入量很大、資料很快就過期,所以放在記憶體裡的空間索引。
- 案例:限時搶購進階
幾十萬人同時搶一百件商品:不能超賣、也不能讓資料庫被打垮。用快取原子地扣庫存、用佇列把訂單的尖峰攤平。
- 案例:分散式鍵值儲存進階
Dynamo 式的鍵值儲存:一致性雜湊決定資料放在哪、Quorum 決定讀寫要幾台確認、gossip 傳遞誰還活著、向量時鐘處理衝突,叢集設定則由 Raft 管理。前面好幾個主題在這裡組成一個完整的系統。
- 案例:協同編輯進階
好幾個人同時編輯同一份文件,每個人都要即時看到別人的修改,最後還要完全一致:OT(Operational Transformation)和 CRDT(Conflict-free Replicated Data Type)兩種做法。
- 案例:檔案同步進階
像 Dropbox 一樣在多台裝置間同步檔案:切成區塊、只傳有變動的部分、相同的內容只存一份,發生衝突時保留兩個版本。
- 案例:通知系統進階
一次要送出幾百萬則推播、簡訊和 email:排隊、限流、重試、去除重複,還要尊重每個使用者的通知設定和勿擾時段。
- 案例:即時熱門排行進階
每秒幾十萬個事件,要隨時算出過去一小時最熱門的前 10 名:串流處理、滑動視窗,以及用 Count-Min Sketch 在固定記憶體裡估計次數。