《Designing Data-Intensive Applications(DDIA)》Podcast 準備稿:分片
書名: 設計資料密集型應用 Designing Data-Intensive Applications(The Big Ideas Behind Reliable, Scalable, and Maintainable Systems) 作者: Martin Kleppmann 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|DDIA.分片:把資料切開,水平擴展的基礎 (6/12) 涵蓋範圍: 第7章 分片(按鍵範圍 vs 按雜湊值、熱點、本地索引 vs 全域索引、重新平衡、請求路由與 ZooKeeper)
背景速覽
上一集把同一份資料複製到多台機器(複製);這一集做相反的事——把一份大資料切開散到多台機器,叫分片(partitioning / sharding)。當資料量或查詢吞吐量超過單機負荷,你就得切。目標是把資料和負載均勻分佈,避免熱點。複製和分片通常一起用:每筆資料屬於一個分片,每個分片再複製到多台機器。這集講分片策略、次要索引的兩難、重新平衡、以及客戶端怎麼找到對的節點。
一句話重點
分片的全部藝術在於均勻——按鍵範圍分片能做高效範圍查詢但容易產生熱點(時間戳全擠在「今天」),按雜湊值分片負載均勻但犧牲範圍查詢;而真正的工程難點不在「怎麼切」,而在切完之後:次要索引怎麼辦(本地索引寫得爽讀要 scatter/gather、全域索引讀得爽寫要更新多片)、節點增減時怎麼搬最少的資料重新平衡、客戶端怎麼知道資料在哪。
值得討論的重點
1. 兩種分片策略:範圍 vs 雜湊,一個換查詢、一個換均勻
- 按鍵範圍分片:每個分片負責一段連續的鍵(像百科全書 A–C 一冊、D–F 一冊)。好處:分片內保持排序,範圍查詢超高效。壞處:容易熱點——經典翻車案例是「鍵是時間戳,所有寫入全擠到今天的分片」。解法:時間戳前面加前綴(如感測器名)。Bigtable、HBase 用這個。
- 按雜湊值分片:用雜湊函數把鍵打散到數值範圍。好處:負載均勻,相似的鍵也散開。壞處:喪失範圍查詢(鍵的排序被打散,範圍查詢得問所有分片)。Cassandra、MongoDB 用這個。
Cassandra 的折衷很聰明:複合主鍵 (user_id, timestamp)——user_id 雜湊決定分片(均勻),timestamp 在分片內排序(可範圍查詢)。魚與熊掌部分兼得。
2. 熱點:連雜湊都救不了的名人問題
雜湊分片也擋不住極端偏斜——名人發一則動態,百萬人讀寫同一個 key,全擠在同一分片。目前多數系統無法自動處理,要應用層緩解:在熱門 key 前後加隨機數(拆成 100 個 key 分散寫入),但代價是讀取要從 100 個 key 彙整、還要額外簿記。這個「名人問題」跟第一集 Twitter fan-out 是同一個幽靈——極端的長尾分布是分散式系統的永恆敵人。
3. 分片與次要索引:本地 vs 全域的兩難
主鍵分片很簡單,但次要索引(查「所有紅色的車」)對不上分片結構。兩條路:
- 按文件分片(本地索引):每個分片維護自己那部分文件的索引。寫入爽(只更新本片索引),讀取痛——紅色的車散在所有分片,得 scatter/gather(問所有分片再彙整),易受尾端延遲放大拖累。但這是實務最常見的(MongoDB、Cassandra、Elasticsearch 都用)。
- 按詞彙分片(全域索引):建一個跨所有分片的全域索引,但這索引本身也要分片。讀取爽(只查含目標詞彙的那片),寫入痛(一筆文件可能要更新多個索引分片),且通常非同步更新(DynamoDB 的全域索引正常幾分之一秒、故障時可能延遲很久)。
4. 重新平衡:先記住一個「錯誤示範」
節點增減、資料成長時,要把資料搬來搬去——重新平衡。先記住一個反面教材:hash(key) mod N 是糟糕的做法——N(節點數)一變,幾乎所有 key 都要搬。正確策略有三:
- 固定分片數:一開始就建遠多於節點的分片(10 節點建 1000 分片),新增節點時「偷」幾個分片過來。分片數不變、鍵到分片的對應不變,只變分片到節點的分配。
- 動態分片:分片長太大就分裂、太小就合併(HBase、RethinkDB),分片數隨資料量自動調節。
- 按節點比例分片:每個節點固定數量分片(Cassandra 預設 256),需搭配雜湊分片。
5. 請求路由:客戶端怎麼找到對的節點
資料散開後,客戶端怎麼知道連哪台?這是服務發現的具體案例,三種做法:任意節點轉發、專門的路由層、客戶端自己感知。三者的核心難題都一樣——做路由決策的那個元件,怎麼得知「分片到節點」的對應變化?
常見答案是 ZooKeeper:節點註冊、ZooKeeper 維護權威對應、路由層訂閱、變化時通知所有訂閱者。HBase、SolrCloud、Kafka 都靠它。Cassandra/Riak 則用 gossip protocol(節點互相傳播狀態)避免外部依賴。
注意事項
⚠️ 全自動重新平衡 + 自動故障偵測 = 危險組合。 Kleppmann 特別警告:某節點只是暫時過載回應慢,其他節點誤判它死了、啟動重新平衡,把更多負載丟給已超載的節點與網路,引發連鎖故障。所以「讓人類參與重新平衡決策」是有價值的——這跟第5集「手動 failover」是同一個智慧:在分散式系統裡,全自動的『聰明』有時候是災難的加速器。
⚠️ scatter/gather 雖然常見,但要對尾端延遲放大有心理準備。 本地索引讀取要問所有分片,只要有一個分片慢,整個查詢就慢——這正是第一集講的「尾端延遲放大」在分片場景的重演。如果你的次要索引查詢延遲不穩定,先懷疑是不是 scatter/gather 在作祟。
⚠️ 「一致性雜湊」這個詞被嚴重濫用。 Kleppmann 直接吐槽:原始定義(Karger 等人,給 CDN 用的)其實對資料庫不好用,多數資料庫用的是更簡單的雜湊分片。所以聽到「一致性雜湊」要先問「你指的是哪一種」——書中建議乾脆直接叫「雜湊分片」避免混淆。這是一個很好的「破除術語迷信」的機會。
專家補充
💡 分片與複製是「互補的兩個維度」,別搞混。 複製是「同一份資料的多個副本」(為了可用性與讀取擴展),分片是「不同資料各放一處」(為了容量與寫入吞吐量)。實務上兩者疊加:每個分片再複製成多副本,每個節點同時是某些分片的 leader、某些分片的 follower。把這兩個維度在腦中分清楚,整個分散式資料庫的架構圖就立體了。
💡 「讓每個分片盡可能獨立運作」是水平擴展的靈魂。 分片之所以能線性擴展,全靠「分片之間互不依賴」。一旦操作要跨多個分片(例如轉帳:一邊扣款一邊入帳,一個成功一個失敗怎麼辦),複雜度就爆炸——這正是下一集「交易」和第9集「分散式事務」要解決的問題。這集的結尾,就是後兩集的開頭。
💡 ZooKeeper 在分散式系統裡是個「隱形的關鍵基礎設施」。 應用工程師很少直接用它,但 HBase、Kafka、Hadoop YARN 背後都靠它管理「誰負責哪個分片、哪些節點還活著」。它的角色是「少量但極度重要的協調中繼資料」的權威來源——這個「少量共識、大量資料」的分層思想,第9集講共識時會正式登場。
討論問題
🎙️ 開場鉤子:「假設你的資料用時間戳當主鍵,看起來很合理對吧?結果上線那天,所有寫入全部擠到同一台機器——因為大家寫的都是『今天』。這就是分片最經典的熱點陷阱,今天我們來看怎麼切資料才不會自爆。」
🎙️ 自問自答:「為什麼不能直接用 hash(key) mod 機器數 來分配資料?這不是最直覺的做法嗎?」回答:因為機器數一變,幾乎所有資料都要搬家——一次擴容就是一場全網大遷徙,系統根本扛不住。
🎙️ 帶走的一題:「如果你的系統有次要索引(不是用主鍵查),想一下它是『每台機器各自建索引』還是『全域一份索引』?前者寫得爽但讀要問所有機器,後者讀得爽但寫要改好幾處——你選的那個,配得上你的讀寫比嗎?」
更大範圍關聯
- 分散式系統三部曲的橋樑:這集(分片)為後面鋪路——分片讓每片獨立、可水平擴展,但「跨分片操作」的難題,直接引出第7集(交易)和第9集(分散式事務、共識)。可以說分片是「把問題切小」,而後幾集是「處理切不開的那部分」。
- 與系統設計面試的分工:面試裡「怎麼 shard 資料庫」幾乎必考。DDIA 這集給你的是完整的判準——範圍 vs 雜湊的取捨、熱點怎麼緩解、重新平衡為什麼不能用 mod、路由怎麼做。你能講出 Cassandra 複合主鍵這種「兼顧均勻與範圍查詢」的巧思,面試官會眼睛一亮。(見 [軟體工程領域切分藍圖] §5「軟體架構演進」)
- 承上:第一集 Twitter 名人 fan-out、這集名人熱點 key——長尾偏斜分布是貫穿全書的反派。每次你以為「均勻分配就好」,總有個百萬粉絲的名人來打破均勻。認得這個幽靈,是系統設計的成熟標誌。
錄製建議
- 建議時長:約 20–22 分鐘。配比:兩種分片策略+熱點約 8 分(含 Cassandra 折衷與名人問題);次要索引的本地 vs 全域約 5 分;重新平衡(含 mod N 反例)約 5 分;請求路由與 ZooKeeper 約 4 分。
- 討論策略:用「時間戳當主鍵→全擠今天」這個熱點陷阱開場,畫面感強、共鳴高。中段把「本地索引 vs 全域索引」用「寫爽讀痛 / 讀爽寫痛」這組對仗講清楚。
hash mod N的反例一定要講——它是「為什麼重新平衡是門學問」的最佳切入點。 - 結尾務必埋好伏筆:「分片讓每片獨立、所以能擴展;但跨分片的操作呢?一邊扣款成功、另一邊入帳失敗怎麼辦?——這就是下一集『交易』要救的命。」讓聽眾有追下去的動力。