跳至主要內容

    單體還是微服務?該不該拆服務,先數你有幾個團隊(2026 架構選擇懶人包)

    預設先做模組化單體,有兩個以上獨立出貨團隊才拆服務

    sunny 桑尼· · 17 MIN READ · 274 次瀏覽

    單體還是微服務?該不該拆服務,先數你有幾個團隊(2026 架構選擇懶人包)
    本文目錄

    大家都說微服務比較現代,你是不是也在猶豫要不要拆?這篇帶你用一個問題決定:你有幾個要獨立出貨的團隊?

    我讀到一則匿名網路貼文,裡面說了一個故事。故事沒辦法核實,細節可能被誇大,我把它當成一則寓言來讀。

    貼文是這樣說的:技術長要求重構十年前的核心系統,唯一的條件是「不能有單點故障」。團隊花半年把一支程式拆成 47 個服務,導入 Kafka、Kubernetes、Redis、整套監控,資料庫、快取、訊息佇列全部做成叢集,架構文件寫了兩百多頁。上線五分鐘後,整個系統掛了。每個 Pod 都是 Healthy,每台資料庫都正常,原因是有人改 DNS 時多打了一個字元,服務們找不到彼此。

    一年後,他們連 DNS 都做了高可用。然後唯一懂整套架構的資深工程師離職了。文件在 GitLab,GitLab 登入要靠 Authentication Service,Authentication Service 的部署方式寫在 GitLab。

    真假先不論,裡面每一步都太合理了,合理到我相信很多團隊正走在同一條路上。

    快速回答 先數你有幾個要獨立出貨的團隊,再決定要幾個服務。預設選模組化單體:程式碼留在同一個部署單位,按業務領域切模組。有兩個以上團隊要用不同節奏上線,或某一塊的負載、故障模式明顯不同,而且邊界在單體裡畫得出來、扛得起分散式營運,才只拆那一塊。拆得開的是程式碼,拆不開的是依賴;AI 讓拆服務變便宜,也讓理解系統變貴。Twilio Segment 拆到 140 多個服務後,最後收回成一個。

    盲目拆微服務不會消滅單點故障,只是把單點搬到 DNS、登入與少數人腦中

    回頭看故事裡的決定,沒有一個是蠢的。服務互相呼叫耦合太高,所以導入事件驅動;部署太麻煩,所以上 Kubernetes;查詢太慢,所以加快取;出錯找不到原因,所以補可觀測性。每個決定都解掉一個看得見的問題。

    問題出在每個解法都帶進一個新的依賴。快取帶進「哪一份資料才是真的」,事件帶進「這筆訂單的 Correlation ID 是多少」,Kubernetes 帶進 Pod 排不上去的那個早上。單點故障沒有被消滅,它從架構圖上看得見的地方,搬到了沒人畫的地方:DNS、登入系統、一個人的腦袋。

    把單體拆成 47 個微服務並不能消滅單點故障,只是把風險從架構圖上搬到沒人畫出來的地方:DNS 設定、認證服務與唯一懂全貌的工程師(參考 Meta 2021 年全球斷線事故與工程復盤案例)。

    現實裡發生過幾乎一樣的事。2021 年 10 月 4 日,Facebook、Instagram、WhatsApp 全球斷線約七小時。Meta 事後的說明寫得很直白:DNS 全面失效,弄壞了平常用來調查和修復事故的內部工具,工程師只好親自跑到資料中心現場重啟系統。修東西的工具,依賴著壞掉的那個東西。

    拆得開的是程式碼,拆不開的是依賴。

    微服務買到團隊獨立上線,代價是網路延遲、資料一致性與維運溢價

    我不反對微服務。它解決的問題是真的:一個團隊改訂單,不必等另一個團隊改會員才能上線;影音轉檔吃滿 CPU,不必連帶擴充整個網站;一個模組出錯,不至於拖垮全部。

    日本二手交易平台 Mercari 從 2018 年起把 PHP 單體遷到 Go 微服務,他們寫得很清楚,動機是組織要長大:每個團隊從設計到維運對自己的服務負責,系統鬆耦合,少依賴其他團隊。這是一個為了讓很多人能同時工作而付的價錢。

    這個價錢有名字。Martin Fowler 叫它 Microservice Premium(微服務溢價):服務之間的網路呼叫、分散的資料一致性、追蹤、部署管線、on-call,全部要有人扛。系統夠複雜、團隊夠多,這筆溢價划算;系統不夠複雜,溢價只會拖慢團隊。他在 2015 年的觀察到今天還成立:成功的微服務故事,幾乎都是從一個長太大的單體拆出來的;一開始就做成微服務的系統,幾乎都陷入麻煩。

    Fowler 用兩條線畫出微服務溢價:系統簡單時,管理微服務的額外負擔讓生產力低於單體;複雜度夠高之後,微服務的低耦合才讓生產力掉得比較少。(圖片來源:Martin Fowler〈MicroservicePremium〉,經 Sunny DataScience 中文重點標註)

    Twilio Segment 是很好的反例。他們為了隔離故障,替每個資料目的地各開一個服務和佇列,最後長到 140 多個。結果三位全職工程師把大部分時間花在讓系統活著,開發速度掉下來,錯誤率衝上去。後來他們把這些服務收回成一個,2018 年寫成〈Goodbye Microservices〉:共用函式庫的改進,一年從 32 項增加到 46 項。他們也誠實寫下代價:故障隔離變難了,一個 bug 可能拖垮整個服務。

    兩個方向都有人付錢。差別在於你付的那筆,換到的東西你用不用得上。

    單體架構省成本的前提是畫出模組邊界,否則只會長成大泥球

    單體的好處很樸素:一次部署、同一個行程裡的函式呼叫、一個資料庫交易、出事時只有一個地方要查。這是單體最被低估的能力。

    2023 年,Amazon Prime Video 的監控團隊發過一篇文章(原文已下架,以下依 DevClass 的報導),說他們把一個原本由 Step Functions 和 Lambda 串起來的影音品質檢測服務,收成單一行程,基礎設施成本降了九成。這篇文章被很多人拿來宣布「微服務已死」。Sam Newman 的提醒比較冷靜:這篇講的主要是函式計價和長駐機器的價差,適用範圍很窄。我同意他。這個案例說明的是「這一個服務切太碎了」,它沒有說 Amazon 該變回一個單體。

    單體的條件是邊界。Shopify 是世界上最大的 Ruby on Rails 程式庫之一,超過一千位工程師在同一份程式碼上工作了十多年。他們評估過微服務,最後選了模組化單體(Modular Monolith):程式碼留在同一個部署單位,按訂單、出貨、庫存、帳務這類業務領域切模組,再用工具強制模組之間的邊界。用他們的話說,要的是更高的模組化,部署單位的數量不增加。

    邊界畫不出來的單體,叫大泥球(Big Ball of Mud,沒有清楚結構、什麼都互相牽動的程式)。把大泥球拆成服務,只會得到一個分散在網路上的大泥球。Mercari 也坦白:面向使用者的功能拆得順利,最複雜、依賴最深的核心交易邏輯,因為領域分析不夠,到他們 2021 年寫那篇回顧時,程式和資料仍然纏在一起。

    把這四家放在一起看,方向各不相同,買到的東西卻都對得上當時的問題:

    公司往哪個方向為了什麼付出或沒解決的

    Mercari
    往哪個方向
    單體拆成微服務
    為了什麼
    團隊變多,要各自負責、各自上線
    付出或沒解決的
    核心交易邏輯沒拆開
    Segment
    往哪個方向
    140 多個服務收回成一個
    為了什麼
    三個人整天在維持系統
    付出或沒解決的
    故障隔離變難
    Prime Video
    往哪個方向
    一個服務從函式收成單一行程
    為了什麼
    函式計價太貴(據報導)
    付出或沒解決的
    只適用這一個服務
    Shopify
    往哪個方向
    留在模組化單體
    為了什麼
    一千多人在同一份程式碼上工作
    付出或沒解決的
    邊界要靠工具強制

    沒有一家是因為「微服務比較現代」或「單體比較簡單」而做決定。他們都先說得出自己的問題,再挑架構。

    AI 讓拆服務變便宜,卻讓跨儲存庫理解與除錯成本暴增

    這一節是我的判斷,也是我寫這篇真正想說的事。

    2026 年,叫 Agent 生成 47 個服務的骨架、Dockerfile、Kubernetes YAML、Helm chart,大概一個下午就有了。故事裡團隊花好幾個月做的拆分,現在的工具讓它便宜到幾乎不用想。

    便宜的是產出,貴的是之後的每一天。每多一個服務,就多一條網路依賴、一份要對齊的資料、一組要有人看的告警。Agent 可以替你寫出排程設定,它不會替你在凌晨三點判斷三個副本為什麼都排不上去。

    CNCF 2025 年的年度調查也指向同一個方向。使用容器的組織裡,82% 已經在正式環境跑 Kubernetes,工具幾乎普及了;受訪者回報的最大挑戰,換成「開發團隊的文化改變」(47%),訓練不足(36%)和複雜度(34%)排在後面。把東西架起來已經不難,卡住團隊的越來越是人和理解。

    還有一件常被忽略的事:Agent 讀程式的方式。在一個模組化單體裡,Agent 可以從一個函式一路追到資料庫,呼叫鏈都在同一個 repo。拆成 47 個服務之後,服務之間怎麼呼叫、傳什麼格式,活在網路上、活在別的 repo、活在某個人的記憶裡。人看不到全貌,Agent 也看不到。

    那個故事的結局,指向的就是這個。整套系統最後的單點故障,是知識只存在一個人身上。AI 讓寫程式的人變多、寫得更快,這個單點只會更危險,除非你把系統怎麼運作寫進 repo,讓下一個人和下一個 Agent 都讀得到。

    架構決策四問:先確認團隊獨立性與負載差異,再評估維運與邊界

    我自己的預設是模組化單體。四個問題分成兩組:前兩題問「要不要拆」,後兩題問「能不能拆」。

    要不要拆

    1. 有沒有兩個以上的團隊,需要用不同節奏獨立上線?只有一個團隊,拆服務多半只是把同一群人的協調,從程式碼搬到網路上。
    2. 有沒有哪一塊的負載或故障模式,和其他部分明顯不同?例如影音轉檔、批次報表,它們需要獨立擴充,或不能拖垮主站。

    兩題都答「否」,就留在模組化單體,把力氣花在模組邊界上。

    能不能拆

    1. 你們扛得起分散式的營運成本嗎?追蹤、on-call、資料一致性、跨服務的測試,要有人和預算。
    2. 這條邊界,在單體裡畫得出來嗎?邊界在程式碼裡站得住,拆出去才有意義。

    需要拆、也拆得動,才只把那一塊拆出去,而且拆出去的服務要有一個叫得出名字的負責團隊。需要拆、卻拆不動,先做領域分析、補營運能力,這段時間仍然留在模組化單體。

    微服務拆分決策流程:先過「要不要拆」(兩個以上獨立出貨團隊或特殊負載),再過「能不能拆」(扛得起分散式維運且單體內邊界清晰),兩關全過才單獨拆分該模組(綜合 Martin Fowler 與 Shopify 實務)。

    這張圖裡最容易憑感覺回答的,是 Q1 和 Q4。「我們有好幾個團隊」「訂單和會員的邊界很清楚」,在會議上說起來都很順,但 git 紀錄會老實告訴你答案:每個模組是哪些人在改,哪幾個模組總是在同一個 commit 裡一起被改。

    十分鐘 Git 紀錄檢查:統計模組共同修改頻率,驗證邊界是否真實存在

    下面這支 Python 不需要安裝任何套件,在 repo 根目錄執行就好。它想驗證兩件事:一、你說的「團隊」,在程式碼上是不是真的各改各的;二、你想拆開的兩個模組,平常是不是各自改得動。跑出來的結果如果和你的直覺不同,該調整的是直覺。

    第一段讀出最近六個月、不含 merge 的 commit,每個 commit 記下作者 email 和改到的檔案。

    import subprocess
    from collections import Counter
    from itertools import combinations
    
    DEPTH = 2  # 模組 = 路徑前兩層,例如 src/orders;依你的目錄結構調整
    log = subprocess.run(
        ["git", "log", "--since=6 months ago", "--no-merges", "--name-only", "--format=%x1e%ae"],
        capture_output=True, text=True, check=True).stdout
    

    第二段把每個 commit 拆成「誰改的」和「動到哪些模組」,再數兩件事:每個模組被多少人改過,以及每一對模組在同一個 commit 裡一起被改的次數。

    commits = []  # (作者, 這個 commit 動到的模組)
    for block in log.split("\x1e")[1:]:
        author, *files = block.strip().splitlines()
        mods = {"/".join(f.split("/")[:DEPTH]) for f in files if "/" in f}
        if mods:
            commits.append((author, mods))
    
    touched, together = Counter(), Counter()
    authors = {}
    for author, mods in commits:
        for m in mods:
            touched[m] += 1
            authors.setdefault(m, set()).add(author)
        for a, b in combinations(sorted(mods), 2):
            together[(a, b)] += 1
    

    第三段印出結果。「一起改的比例」用比較小的那個模組當分母:小模組的改動有多少比例會牽動另一個。

    print("module, commits, authors")
    for m, n in touched.most_common(10):
        print(f"{m}, {n}, {len(authors[m])}")
    
    print("\npair, changed together, share of the smaller module's commits")
    for (a, b), n in together.most_common(10):
        print(f"{a} + {b}, {n}, {n / min(touched[a], touched[b]):.0%}")
    

    讀結果的方法很簡單,沒有標準門檻,看的是相對高低:

    • 第一張表的 authors 欄,是 Q1 的線索。如果每個模組都是同一批人在改,你們在程式碼上其實是一個團隊,拆服務不會讓誰變得更獨立。
    • 第二張表是 Q4 的線索。你想拆開的那一對模組,如果一起改的比例明顯高於其他組合,代表這條邊界在程式碼裡還沒站住。這時候拆出去,每次改動都會變成兩個服務、兩條部署線一起動。
    • 這是近似值。一個 commit 同時改兩個模組,不一定代表它們互相依賴;作者 email 也只能粗略代表團隊。它的用處是讓會議上的討論,從「我覺得」變成「這半年的紀錄是這樣」。

    iThome 2019 年報導 Kubernetes Summit 時,訪問了曾在 Best Buy 擔任電商架構總監 13 年的 Kevin Matheny(當時是 Gartner 資深研究總監)。他再三強調「微服務架構不應該是企業的目標」,建議企業別先問怎麼擁抱微服務,先問自己有什麼問題需要靠微服務解決。七年過去,這個提醒沒有過時,只是現在多了一個會幫你拆的 AI,讓人更容易跳過這個問題。

    結論:先數你有幾個獨立出貨的團隊,再決定拆幾個服務

    讀完那篇貼文,我最在意的是那份兩百多頁的架構文件。每一個看得見的單點都被消滅了,看不見的那幾個,從頭到尾沒有寫進去。

    架構圖上消失的單點,通常搬進了沒人畫的地方。選單體或選微服務之前,先把那些地方畫出來:DNS、登入、部署腳本、文件放在哪、那個唯一懂全部的人。畫完再回頭看,我猜多數團隊會發現,真正需要獨立出去的只有一兩塊。

    先數你有幾個要獨立出貨的團隊,再決定你要幾個服務。

    如果你的系統正在交給 Agent 接手,下一步可以讀〈AGENTS.md 怎麼寫?規則寫越多,Agent 反而越貴?(2026 完整教學+範例)〉,把系統怎麼運作寫進 repo。

    常見問題

    什麼是模組化單體(Modular Monolith)?

    程式碼留在同一個部署單位,按訂單、出貨、庫存、帳務這類業務領域切模組,再用工具強制模組之間的邊界。Shopify 有超過一千位工程師在同一份 Ruby on Rails 程式碼上工作,評估過微服務後選了這條路:要更高的模組化,部署單位的數量不增加。

    什麼時候該拆微服務?

    先問要不要拆:有沒有兩個以上的團隊要用不同節奏獨立上線,有沒有哪一塊的負載或故障模式和其他部分明顯不同。任一題是,再問能不能拆:扛不扛得起追蹤、值班(on-call)、資料一致性這些營運成本,這條邊界在單體裡畫不畫得出來。兩關都過,才只拆那一塊,並交給一個叫得出名字的團隊負責。

    Amazon Prime Video 改回單體,代表微服務過時了嗎?

    不能這樣推。2023 年 Prime Video 把一個由 Step Functions 和 Lambda 串起來的影音品質檢測服務,收成單一行程,基礎設施成本降了九成(原文已下架,依 DevClass 報導)。Sam Newman 提醒,這主要是函式計價和長駐機器的價差,適用範圍很窄:它說明的是這一個服務切太碎了。

    怎麼知道模組邊界夠不夠清楚、能不能拆?

    看最近六個月的 git 紀錄:每個模組被多少人改過,哪幾對模組常在同一個 commit 裡一起被改。想拆開的那一對,一起改的比例明顯高於其他組合,可能代表邊界還沒站住,拆出去後常得兩個服務、兩條部署線一起動。這是近似值,用處是讓討論從「我覺得」變成看紀錄。

    用 AI 拆微服務變簡單了,還要擔心什麼?

    AI 讓拆服務變便宜,讓理解系統變貴。叫 Agent 生成幾十個服務的骨架和 Kubernetes 設定,大概一個下午就有了;之後每多一個服務,就多一條網路依賴、一份要對齊的資料、一組要有人看的告警。服務之間怎麼呼叫,活在網路和別的 repo 裡,人看不到全貌,Agent 也看不到。

    延伸閱讀

    參考資料

    留言0

    登入後就能留言。

    登入