垂直擴展跟水平擴展的判斷樞紐是一個問題:這個元件能不能做成無狀態。能做成無狀態的,加實例(水平)幾乎可以無腦複製;做不成、或改造成本太高的,只能換更大的機器(垂直)先撐。這個模組前面幾章一直在講怎麼做到無狀態,正是因為無狀態與否是這個決策的關鍵——一個服務落在哪一邊,決定了它該往哪個方向擴。

兩種擴展的機制、各自的物理與成本天花板,在 規模拐點判斷 的垂直對水平段展開過(垂直有兩道牆——換更大的機器會撞到物理規格上限、以及成本在高階機型非線性飆升;水平則有協調與連線放大成本)。這一章不重述那些機制,聚焦在「怎麼用無狀態與否做這個判斷」。

先問能不能無狀態

判斷從一個問題開始:這個要擴的元件是無狀態的、還是有狀態的。無狀態的部分(API server、worker)水平擴展幾乎無腦——每個實例對等、加一台就多一台的容量,這個模組前面幾章的設計就是為了讓服務落在這一邊。有狀態的部分(資料庫、快取、session store)不能靠複製水平擴展,因為狀態不對等——不能開兩台資料庫主庫同時寫、期待它們自己一致。

把這個前提判反,是水平擴展最常見的撞牆方式:用水平擴展的策略去動一個有狀態的服務,加了實例卻發現狀態沒有跟著分攤,QPS 沒漲、甚至因為協調成本更慢。判斷的第一步永遠是先分清楚:這個元件是無狀態的(水平的候選)、還是有狀態的(要另一套策略)。

有狀態的節點:垂直撐,讀路徑用副本擴

有狀態的節點(典型是資料庫主庫)擴展走另一條路。寫入的部分靠垂直撐——換更大的機器,不改程式、立即見效,適合這種不能水平複製的關鍵節點。但垂直有天花板,且有狀態的關鍵節點常常比機器規格更早撞牆(交易型資料庫主庫的真實上限往往卡在架構因素、不是規格不夠),這條在拐點判斷那章講過。

垂直撐主庫的同時,讀路徑可以水平擴——加唯讀副本分攤讀流量,這是 Shared storage 選型 的讀寫分離。所以一個有狀態的資料庫,實際的擴展是混合的:寫路徑垂直撐、讀路徑用副本水平擴。垂直撐到頂、讀副本也不夠時,最後一步是分片(sharding)——把資料按某個鍵拆到多台,每台只管一部分,這才是有狀態服務真正的水平擴展,但它要重新設計資料的切分方式,成本最高、留到前面手段都用盡才動。

混合是常態,不是二選一

真實系統極少是純垂直或純水平,而是按層混合:無狀態的應用層水平擴(加實例)、有狀態的資料層垂直撐加讀副本(撐不住再分片)。這對應一個擴展框架的三個方向——複製(把無狀態的元件多開幾份)、功能拆分(把不同功能拆成獨立服務、各自按自己的需求擴)、資料分片(把有狀態的資料按鍵拆開),常常同時在動、不是選一個。這個框架(AKF Scale Cube)的完整拆解在 backend 擴展軸

判斷的收斂是這樣:先問這一層能不能無狀態。能,就水平複製,這是最便宜的擴展。不能,先看改造成無狀態的成本值不值得;不值得或改不動,就垂直撐這個節點、讀路徑用副本水平擴、撐到極限再分片。每一步都是在「這個元件的狀態能不能被分攤」這條線上做選擇——這也是為什麼水平擴展這個模組,從頭到尾都在講無狀態。

下一步路由