【從一台 Server 到分散式架構】第 11 篇:單體開始痛苦——一改 code 全站都要 deploy
小明他們的線上課程平台,現在已經跨出了台灣,變成一個橫跨美、亞兩洲的全球化系統了。
為了應付全球流量,他們在上一篇(第 10 篇)討論了跨區的 CAP 取捨:在台灣部署了 Primary DB,在美國部署了資料複本(Replica),並且根據業務重要性決定了 CP(強一致)與 AP(最終一致)的策略。在「基礎建設」與「資料一致性」上,這套系統已經相當成熟。
但在開發效率與部署穩定性上,小明和他的團隊卻開始感到前所未有的痛苦。
原因很簡單:雖然基礎設施已經分散在全球各區,但核心的後端程式仍然是一個巨大的 單體架構(Monolith)。
🏗️ 本篇開始前的架構 — 全球部署的「單體大程式」
雖然我們在多個地區部署了伺服器和資料庫,但每個地區跑的「後端 API」仍然是同一份巨大的程式碼。
什麼叫「單體痛苦」?
一開始,小明只有三、四個工程師。大家改同一份 code、跑同一個 CI/CD 流程,覺得很方便。但當團隊成長到二十人、三十人,負責的功能切成「使用者組」、「課程組」、「金流組」時,問題就來了:
1. 「一帶全」的部署壓力(All or Nothing)
金流組只是想改一個退款的小 bug,理論上只需要動到幾行 code。但因為所有功能都包在一起,他們必須部署整個網站。
- 每次 deploy 都要把整包幾百 MB 的程式重傳、重新編譯。
- 部署時間從幾分鐘變成幾十分鐘,開發效率大幅下降。
2. 「連環爆」的連帶風險
某天課程組改壞了一個「取得推薦列表」的功能,沒想到這個邏輯被包裹在全站通用的 Utils 裡,結果導致連登入功能都壞掉。
在單體架構中,所有模組都在同一個 Process 裡跑,記憶體與 CPU 是共用的。一個模組發生記憶體洩漏(Memory Leak)或無窮迴圈,整台 Server 就會一起掛掉。你很難做到「局部隔離」。
3. 程式碼的「萬能醬汁」(Big Ball of Mud)
隨著時間過去,課程、訂單、使用者、評論……這些邏輯開始互相依賴。
Order類別裡直接呼叫了CourseService的私有 method。User類別被傳來傳去,到處都是 side effect。 邊界越來越模糊,新進工程師根本不敢改 code,因為不知道動了這裡會不會噴掉那裡。這就是俗稱的「大泥球(Big Ball of Mud)」。
生活化的比喻:大廚房 vs 獨立工作室
我們之前都用「廚房」來比喻後端,現在我們來看看廚房變大的問題。
單體架構就像一間超級巨大的廚房。這裡有幾十個廚師,有的烤麵包、有的炒青菜、有的切刺身、有的煮熱湯。
- 改 code 的痛苦:如果你想把烤箱換掉(改金流邏輯),整間廚房必須停工清場,所有廚師都要停下手邊動作等烤箱換好。
- 連環爆的風險:炒青菜的廚師不小心失火了,因為大家都在同一個開放空間,濃煙會立刻燻到刺身區和麵包區,導致整間餐廳今天都不能營業。
- 管理的混亂:大家共用同一個洗碗槽、同一個冰箱。有人把生魚片放在甜點區,有人把洗手水噴到湯裡。
微服務架構則是把這間大廚房拆掉,變成一整條街上的多個專門工作室:
- 有專門的麵包店、專門的熱炒店、專門的壽司店。
- 麵包店想換烤箱?他們自己關門半天就好,隔壁的熱炒店照樣可以炒餐。
- 熱炒店失火了?只要門關好,不會影響到其他店。
- 每間店有自己獨立的冰箱(資料庫)和領班。
為什麼現在才要拆?
你可能會問:「既然單體這麼痛苦,為什麼小明一開始不直接做微服務?」
這就是系統設計最重要的觀念:微服務不是免費的午餐。
拆分服務會帶來極高的維運複雜度:
- 你要維護多個 CI/CD 流程。
- 你要處理服務之間的通訊(Network I/O 代替了 Function Call)。
- 出問題時,你要追查這筆請求到底是卡在麵包店還是熱炒店(分散式追蹤)。
單體架構是「好開發、難擴大規模」;微服務是「難開發、好擴大規模」。 只有當你的團隊規模和流量大到「單體帶來的開發阻礙」已經超過「維運微服務的成本」時,拆分才是有意義的。
接下來的挑戰
小明團隊意識到,他們不能再讓「全站 deploy」成為日常。他們決定開始挑選最獨立的功能,嘗試切出第一個「微服務」。
🏗️ 本篇結束後的架構 — 識別出高度耦合,準備切分
雖然目前物理部署(台灣/美國)沒變,但我們在意識上已經要把 Monolith 內部的「功能群」標示出來,準備進行手術。
要把這些「黏在一起」的功能分開,並不是把程式碼剪剪貼貼到新專案就好,小明他們馬上就面臨了幾個實務上的「大坑」:
- 資料庫的「連理枝」:目前的單體共用同一個資料庫、同一個 Schema。如果把課程邏輯拆出去,那課程資料表(Course Table)要不要也搬走?如果搬走了,訂單要查詢課程名稱時,該怎麼做 Join?
- 呼叫方式的改變:在單體裡,A 功能找 B 功能只是內部的
Function Call,速度極快且保證成功。拆開後,變成跨網路的HTTP Call或gRPC。網路可能會斷、可能會慢,小明必須開始處理「逾時(Timeout)」與「重試(Retry)」的問題。 - 交易(Transaction)的遺失:過去「下單 + 扣課程配額」可以在同一個資料庫 Transaction 裡完成,要嘛一起成功,要嘛一起失敗。拆成兩個服務後,如果錢扣了但課程配額沒扣到,該怎麼辦?
劃分服務邊界(Service Boundary),並且在拆分後還能讓各個工作室順暢溝通,就是小明下一個階段要修煉的課題。
小結與預告
今天我們聊了單體架構在系統長大與團隊擴編後的三大痛苦:
- 部署壓力:一改 code 就要整站重新 deploy。
- 風險連帶:一處出錯,全站掛掉,缺乏故障隔離。
- 代碼耦合:邊界模糊,變成牽一髮而動全身的「大泥球」。
雖然微服務是解藥,但它也是一劑強效藥,伴隨著昂貴的維運代價。 下一篇,我們將跟著小明拆出第一個服務,看看「API Gateway」這個關鍵帶位員如何登場。