從單體到微服務的演進
前言
沒有哪個架構是「最好的」,只有「最適合當前階段的」。 從單體到微服務不是一步到位的跳躍,而是隨著業務規模和團隊規模增長,逐步演進的過程。過早拆分微服務和過晚拆分一樣危險。
這篇文章會帶你學什麼?
學完這章後,你將獲得:
- 演進路徑:理解從單體到微服務的四個階段
- 拆分時機:知道什麼時候該拆、什麼時候不該拆
- 拆分策略:掌握按業務域拆分的方法論
- 通訊模式:了解服務間同步和非同步通訊的選擇
- 資料拆分:理解資料庫拆分的挑戰和方案
| 章節 | 內容 | 核心概念 |
|---|---|---|
| 第 1 章 | 架構演進路徑 | 單體→模組化→SOA→微服務 |
| 第 2 章 | 拆分時機與原則 | Conway 定律、團隊自治 |
| 第 3 章 | 拆分策略 | DDD 限界上下文、絞殺者模式 |
| 第 4 章 | 服務通訊 | REST、gRPC、訊息佇列 |
| 第 5 章 | 資料拆分 | 資料庫拆分、資料同步 |
1. 架構演進路徑
架構演進不是技術驅動的,而是組織規模驅動的。當團隊從 5 人增長到 500 人時,單體架構的協作效率會急劇下降。
| 階段 | 架構 | 團隊規模 | 特點 |
|---|---|---|---|
| 起步期 | 單體應用 | 1~10 人 | 所有程式碼在一個專案中,部署簡單 |
| 成長期 | 模組化單體 | 10~50 人 | 程式碼按模組劃分,但仍然一起部署 |
| 擴張期 | SOA(面向服務) | 50~200 人 | 按業務線拆分為粗粒度服務 |
| 規模期 | 微服務 | 200+ 人 | 細粒度服務,每個團隊獨立開發部署 |
Conway 定律
「設計系統的組織,其產生的架構等同於組織的溝通結構。」——Melvin Conway
簡單說:3 個團隊做一個系統,最終會變成 3 個服務。架構拆分本質是組織拆分。
反向 Conway 定律:既然組織結構決定了系統架構,那麼想要什麼樣的架構,就先調整成什麼樣的組織結構。比如你想拆出獨立的支付服務,就先組建一個獨立的支付團隊。很多公司微服務拆分失敗,不是技術問題,而是組織沒有跟著調整。
2. 什麼時候該拆微服務?
不是所有系統都需要微服務。過早拆分會帶來不必要的複雜性。
| 訊號 | 說明 | 建議 |
|---|---|---|
| 部署衝突頻繁 | 多個團隊改同一個程式碼庫,經常衝突 | 考慮拆分 |
| 某模組需要獨立擴容 | 搜尋模組需要 10 倍於其他模組的資源 | 考慮拆分 |
| 技術棧需要差異化 | AI 模組用 Python,主站用 Java | 考慮拆分 |
| 團隊 < 10 人 | 溝通成本低,單體足夠 | 不要拆 |
| 業務還在探索期 | 需求變化快,邊界不清晰 | 不要拆 |
| 沒有 DevOps 能力 | 沒有 CI/CD、容器化、監控體系 | 不要拆 |
3. 拆分策略
3.1 按業務域拆分(DDD 限界上下文)
DDD(領域驅動設計)的限界上下文(Bounded Context)是拆分微服務的最佳指導原則。每個限界上下文對應一個獨立的業務域,有自己的資料模型和業務規則。
什麼是限界上下文? 同一個詞在不同業務域中含義不同。比如「使用者」在使用者域是指註冊資訊(姓名、信箱),在訂單域是指下單人(收貨地址、支付方式),在推薦域是指行為畫像(瀏覽歷史、偏好標籤)。限界上下文就是劃定一個邊界,在這個邊界內,術語和模型有明確統一的含義。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 使用者域 │ │ 訂單域 │ │ 支付域 │
│ │ │ │ │ │
│ User │ │ Order │ │ Payment │
│ Profile │ │ OrderItem │ │ Refund │
│ Address │ │ Cart │ │ Transaction │
│ │ │ │ │ │
│ 使用者服務 │ │ 訂單服務 │ │ 支付服務 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────── API 呼叫 / 事件通訊 ───────┘| 限界上下文 | 核心實體 | 對應服務 |
|---|---|---|
| 使用者域 | User、Profile、Address | 使用者服務 |
| 商品域 | Product、Category、SKU | 商品服務 |
| 訂單域 | Order、OrderItem | 訂單服務 |
| 支付域 | Payment、Refund | 支付服務 |
| 物流域 | Shipment、Tracking | 物流服務 |
3.2 絞殺者模式(Strangler Fig Pattern)
不要一次性重寫整個單體,而是像絞殺榕一樣,逐步用新服務替換舊模組:
- 在單體外部建立新服務
- 透過代理層將部分流量路由到新服務
- 驗證新服務穩定後,逐步遷移更多流量
- 最終完全替換舊模組
4. 服務通訊模式
| 方式 | 協定 | 特點 | 適用場景 |
|---|---|---|---|
| REST | HTTP/JSON | 簡單通用,生態好 | 對外 API、CRUD 操作 |
| gRPC | HTTP/2 + Protobuf | 高效能,強型別 | 內部服務間高頻呼叫 |
| 訊息佇列 | AMQP/Kafka | 非同步解耦,削峰填谷 | 事件通知、非同步任務 |
| GraphQL | HTTP/JSON | 客戶端按需查詢 | BFF 層、行動端 |
同步 vs 非同步的選擇
- 需要立即回傳結果 → 同步(REST/gRPC)
- 不需要立即回傳 → 非同步(訊息佇列)
- 一個事件觸發多個動作 → 非同步(發布-訂閱)
經驗法則:能非同步就非同步,同步呼叫鏈越長,系統越脆弱。
5. 資料拆分:最難的部分
微服務拆分中最痛苦的不是程式碼拆分,而是資料庫拆分。每個服務應該擁有自己的資料庫,但這意味著跨服務查詢變得困難。
| 挑戰 | 描述 | 解決方案 |
|---|---|---|
| 跨服務 JOIN | 不能直接 JOIN 兩個服務的表 | API 組合查詢、資料冗餘 |
| 分散式事務 | 跨庫事務無法用本地事務 | Saga、本地訊息表 |
| 資料一致性 | 多個服務的資料可能暫時不一致 | 最終一致性、事件驅動 |
| 資料遷移 | 從共享庫遷移到獨立庫 | 雙寫過渡、資料同步工具 |
總結
從單體到微服務是一個漸進的過程,不是一蹴可幾的革命。
回顧本章的關鍵要點:
- 演進路徑:單體→模組化單體→SOA→微服務,每一步都有明確的驅動力
- 拆分時機:團隊規模、部署衝突、擴容需求是拆分的訊號
- 拆分策略:用 DDD 限界上下文指導拆分,用絞殺者模式漸進遷移
- 通訊選擇:能非同步就非同步,同步呼叫鏈越短越好
- 資料拆分:最難但最重要,接受最終一致性是關鍵心態轉變
延伸閱讀
- Building Microservices - Sam Newman 微服務經典
- Monolith to Microservices - 漸進式遷移指南
- Domain-Driven Design - Eric Evans 的 DDD 經典
- The Strangler Fig Pattern - Martin Fowler 的絞殺者模式