PCA 雲端架構師之旅 05 — Microservices 拆分
「我們要微服務化」這句話,我聽過太多次了。然後一上雲就把單體(monolith)一刀剁成 20 個服務,每個都得跟另外 19 個講話,最後變成分散式單體(distributed monolith)——明明拆開了,卻比原本還難改、還難測、還難救。
這是微服務最大的陷阱:拆得動,不代表拆得對。PCA 考試很愛在這裡設局,題目會描述一堆現象,要你判斷邊界該畫在哪、服務之間怎麼溝通,而不是看你會不會一鍵開 GKE。
這是 PCA 雲端架構師之旅 的第五步。上一篇我們把 SLO / SLI 訂好了(04 · SLO / SLI),這一步就是把那些 SLO 跟前面的 user story,落成一張實際的服務地圖。

圖解:把程式切成很多份,卻讓所有服務共用同一個資料庫、彼此到處同步呼叫,只是把單體變得更難救。好的邊界跟著商業能力與團隊責任切,每個服務掌握自己的資料,只在真的需要時透過簡單介面或事件交換資訊。
題目其實一直在偷偷告訴你邊界在哪
PCA 的 case study 不會直接寫「請拆成 N 個服務」,它會藏在敘述裡。你會看到這類句子:
- 「某個團隊希望能獨立部署、不受其他團隊發版節奏影響」
- 「促銷模組每週都在改,但訂單核心半年才動一次」
- 「某個功能的尖峰流量是其他功能的 10 倍」
這每一句,都是在劃服務邊界。一個團隊想獨立部署,就是在說「這塊該是自己的服務」;變更速率差這麼多,就是在說「別綁在一起發版」;流量差 10 倍,就是在說「scaling 特性不同,分開才好擴」。讀題的時候把這些線索圈出來,答案幾乎自己就浮出來了。
最怕的是看到「拆」就反射性地全塞 GKE、全部拆到底。考試要的是合理的拆法,不是最多服務的拆法。
📝 考場提點
看到「獨立部署」「不同發版頻率」「流量差好幾倍」「這個模組碰信用卡 / 病歷」這幾種字眼,先在腦中標記「這裡有一條邊界」。考題給的選項通常會有一個「全部塞同一個服務」跟一個「拆到奈米級」的極端,這兩個多半是陷阱——正確答案常常落在中間那個「照業務能力分、數量適中」的選項。別被「微服務 = 越多越潮」帶偏。
拆之前,先搞懂你是照什麼軸在切
新人最常見的災難,是用錯的軸去拆。我看過兩種,都很慘:
一種是按技術層拆——資料層一個服務、邏輯層一個服務、前端一個服務。聽起來很整齊,但這根本是把三層式架構畫成三個盒子而已,每改一個小功能還是得三個一起動,等於沒拆。
另一種是按資料表拆——一張表配一個服務。結果下一筆訂單要連續呼叫十個服務,網路來回的延遲全疊上去,還隨便哪個掛了整單就死。
真正該看的是這三條軸:
- 按業務能力(business capability)切。 一個服務對應一件業務上說得出口的事——「下單」「查庫存」「算促銷價」。標準很簡單:你能不能用一句業務的話介紹這個服務在幹嘛?講不出來,多半切錯了。
- 按變更速率(rate of change)切。 天天在改的跟半年不動的,硬綁在一起就互相拖累——少動的那塊被迫跟著一直重新測試、重新部署。分開才能各走各的節奏。
- 按擴展特性(scaling profile)切。 讀多寫少、計算密集、I/O 密集,這三種要的機器資源完全不同。混在一個服務裡,你連 autoscaling 都很難調。
這三條軸常常會打架——某塊照業務能力該合,照變更速率又該分。沒有標準答案,這時候就回去看你的 SLO 跟團隊現實,哪條軸對這個系統最痛就聽哪條的。
DDD 那幾個詞,講白話就好
聊微服務邊界一定會碰到領域驅動設計(Domain-Driven Design,DDD)。它的詞彙聽起來很學術,但意思其實很樸素:
Bounded Context(限界上下文),就是「一塊領域的地盤」。訂單是一塊、庫存是一塊、會員是一塊,各自有各自的規則跟語言——同樣叫「商品」,在 catalog 裡指的是可以被搜尋瀏覽的東西,在 inventory 裡指的是有幾件可賣的數字,這兩個其實是不同的東西。一個 bounded context 通常就對應一個微服務,這是最自然的起手式。
Aggregate(聚合),是「一份要綁在一起保證一致的資料」。一張訂單底下有品項、金額、狀態,這些得一起改、一起對——你不能訂單金額改了品項沒改。這一坨就是一個 aggregate,而 aggregate 不能跨服務切開,不然你就得自己手刻分散式交易,那是另一個地獄。
Context Map(上下文地圖),就是把各塊地盤之間的關係畫成一張圖:誰呼叫誰、誰訂閱誰的事件、哪兩塊其實該合併。畫出來你才看得到整體耦合長怎樣。
實務上一個小技巧:aggregate 的邊界往往就是你的服務邊界。先找出「哪些資料必須強一致」,把它們圈成一個 aggregate,服務邊界八九不離十就跟著出來了。
服務之間怎麼講話,比拆幾個還重要
拆完之後真正考驗架構功力的,是服務之間怎麼溝通。這塊選錯,前面拆得再漂亮都白搭——我看過拆得很乾淨的系統,因為全部用同步呼叫串起來,結果一個下游慢三秒、整條鏈全部 timeout,比單體還脆弱。
幾種主要的溝通方式,以及在 GCP 上對應的服務:
| 模式 | 什麼時候用 | GCP 對應 |
|---|---|---|
| 同步 REST / gRPC | 即時要結果、強依賴 | Cloud Run + Internal ALB、GKE |
| 非同步事件(Pub/Sub) | 可以解耦、容忍一點延遲 | Pub/Sub |
| 工作佇列 | 要重試、要限流 | Cloud Tasks |
| 批次資料管線 | 大量、非即時 | Dataflow、BigQuery |
選的訣竅其實一句話:能不等就別等。使用者真的需要當下拿到結果(例如結帳前查庫存夠不夠),才用同步呼叫;只要是「做完通知一聲就好」的事(例如訂單成立後寄通知、更新搜尋索引),就丟事件,讓下游自己慢慢消化。

別被「丟事件,下游自己慢慢消化」騙了——「消化」背後的眉角全在這張訂閱設定裡。Pub/Sub 預設是「至少一次」送達,訊息可能重送,所以消費端一定要做成 idempotent(同一筆付款重複收到也不會重複扣款),這是 PCA/PDE 最愛埋的陷阱。確認期限(ack deadline)這裡設 10 秒,超時沒回 ack 就重送;dead-letter 則讓一直處理失敗的「毒藥訊息」別無限重試、卡死整條訂閱。至於「僅傳送一次」和「訊息排序」聽起來很香,但都會犧牲吞吐、還有同區域等限制,別反射性地全開。
過來人的血淚是:同步呼叫鏈越短越好。A 同步呼叫 B、B 再同步呼叫 C、C 又呼叫 D,這條鏈的總延遲是每一段相加,而且任何一段抖一下、整條都受影響。一個經驗法則是超過 3 跳就該停下來想:這幾段裡有沒有哪一段其實可以改成事件、不用卡在那裡等?
走一遍範例 — 登雲書店
光講原則太抽象,回到我們的老朋友登雲書店。後面幾步也會一直用到這份拆法,這裡先把它讀熟。
根據前面幾篇定下來的 persona、user story 跟 SLO,登雲書店切出 8 個核心服務,每個對應一個 bounded context:
| # | 服務名 | 職責 | 變更速率 | SLO 重點 |
|---|---|---|---|---|
| 1 | catalog-service | 商品目錄、搜尋 | 中(每週) | 讀多寫少、99.9% |
| 2 | inventory-service | 庫存數量、預留 | 低(每月) | 強一致、99.99% |
| 3 | pricing-service | 定價、促銷 | 高(每日) | 讀多寫少、99.9% |
| 4 | cart-service | 購物車 | 中 | 高 QPS、99.9% |
| 5 | order-service | 訂單生命週期 | 低 | 99.99%、Saga 協調 |
| 6 | payment-service | 金流整合 | 低 | PCI 隔離、99.99% |
| 7 | ebook-service | 電子書閱讀進度 | 中 | 寫多讀多、99.9% |
| 8 | partner-ingest-service | 書商批次匯入 | 低 | 吞吐導向、99% |
為什麼是這樣切,每一刀都有理由:
catalog 跟 inventory 一定要分開。 這是最容易被新人合在一起的兩個——它們都叫「商品」嘛。但 catalog 被大量快取、流量超高、讀多寫少;inventory 要強一致才不會超賣,寫入路徑得謹慎。一個追求快取命中率、一個追求一致性,scaling 跟設計取向完全相反,綁一起兩邊都做不好。
pricing 獨立出來。 促銷規則三天兩頭在改,行銷常常今天談好一檔活動明天就要上。如果它跟訂單、庫存綁死,每次改個折扣都要連動部署一堆東西,沒人敢動。拆開來,pricing 自己每天發版都不關別人的事。
payment 不只獨立,還放在獨立的 GCP project。 這是 PCI DSS 的硬要求——卡號接觸的範圍要降到最小(minimized scope)。把金流關進自己的 project,就能精準控制「只有這個 project 碰得到卡號」,稽核範圍也跟著縮小,未來過 PCI 評估會省下大把力氣。這一刀切的不只是技術,是合規。
partner-ingest 獨立。 書商批次匯入是典型的「突然來一大坨、平常很閒」的工作型態,跟線上即時服務的資源曲線天差地遠。放在一起,一場大批次匯入就可能把線上服務的資源吃光。隔出來,互不干擾。
服務切好了,接著看它們怎麼互相溝通:
- 同步呼叫用在使用者真的在等結果的地方:cart → pricing(顯示現價)、cart → inventory(結帳前確認還有貨)。
- 非同步事件用在不該卡住主流程的地方。order → payment 走 Pub/Sub:order 把付款請求丟出去就先去處理別的,不會傻等銀行 API 回那 20 秒;payment 處理完再發事件通知 order 更新狀態。
- 事件廣播用在「一件事發生、好幾個人都想知道」的場景:inventory 一變動就發
inventory.updated,catalog 跟搜尋索引各自訂閱、各自更新,彼此不用認識對方。 - 批次管線給 partner-ingest 用:透過 Dataflow 把書商資料寫進 inventory 與 pricing 的專屬 import table,校驗無誤後再交換進正式表,避免髒資料直接污染線上。
最後是一些跨服務的共同規矩,這在大團隊很關鍵:
- 每個服務一個 GCP project(或至少一個 namespace),用 Workload Identity Federation for GKE(Google 2024 年把原本的 Workload Identity 併入這個名稱)管 IAM,服務身分清清楚楚。
- 服務間呼叫走 Internal Application Load Balancer(內部 ALB,舊稱 Internal HTTP(S) LB)或 gRPC,不暴露到公網;對外統一只開 API Gateway 一個門。
- 共用的資料格式放在同一個 repo 的
schemas/目錄,用 Protobuf 加 Buf 做版本控管,避免某個服務偷改欄位把下游全打掛。
每做一個拆分決定,回頭對一次題目給的需求:這刀是真的對應到某個業務能力 / SLO / 合規要求,還是只是我覺得拆開比較整齊?後者就是過度設計。
拆過頭,和拆不夠,都會出事
拆得對不對,有幾條界線你心裡要有數:
別拆成奈米服務。 8 個對登雲書店剛好,30 個就是自找麻煩——服務一多,光是服務之間的呼叫、監控、部署協調就能把團隊淹死。我的建議向來是寧可先粗一點,從 3 到 5 個起步,跑一陣子看哪裡真的痛、真的該獨立了,再拆。拆開永遠比合併容易。
兩個服務共用同一個資料庫,等於沒拆。 這是最隱蔽的假微服務:表面上兩個服務,底層共一張表,改個 schema 兩邊一起爆。每個服務必須擁有自己的儲存,這條沒有商量餘地——資料的 owner 只能有一個。
同步鏈別超過 3 跳。 前面講過,A → B → C → D 的同步串接,一個慢全部慢。超過 3 跳就回頭看看哪段能事件化。
還有一條最容易被工程師忽略的——Conway’s Law。 這條定律白話講就是:系統最後會長得像你的組織。一個 6 人的維運團隊,你硬切 8 個服務,平均一人顧一個還有剩,半夜誰的服務出事、誰來救?沒人。拆分數量得跟團隊能扛的量級對齊,這不是技術問題,是現實問題。考試裡只要 case study 提到團隊規模很小,「先別拆太細」往往就是更穩的答案。
📝 考場提點
「拆太細」在考場是高頻陷阱。題目給你一個小團隊、卻有個選項把系統切成十幾個微服務,那個選項通常就是來釣你的——再加上 Conway’s Law 一想就知道顧不過來。同樣地,看到「共用資料庫」「同一張表多服務寫入」的選項,幾乎都是錯的,因為它違反「一份資料一個 owner」。時間上,這類題不必糾結到完美邊界,抓住「按業務能力拆 + 數量適中 + 各服務獨享儲存」三個原則就能穩穩拿分,省下的時間留給後面的計算題。
延伸閱讀
- Microservices.io — Decomposition Patterns — Chris Richardson 的經典拆分方法。
- GCP — What Is Microservices Architecture? — 官方微服務架構概念說明。
- DDD Reference by Eric Evans — DDD 用語官方 PDF。
- SRE Workbook — Non-Abstract Large System Design — 系統分解的 SRE 觀點。
下一步:服務邊界有了,接著就要設計它們對外講話的契約,也就是 REST API 設計。
🎯 換你練習
理論讀完,換自己來。到 架構師設計工作坊 · 步驟 5 填入你的 case study,邊寫邊內化。