服務拆分是微服務架構設計的核心議題。拆分得當可以帶來敏捷性與可維護性的提升;拆分不當則會引入不必要的分散式複雜度。本章節將深入探討何時拆分、如何拆分,以及拆分後的服務分層策略。
何時進行服務化拆分#
項目發展階段分析#
第一階段:快速驗證期
項目初期的主要目標是快速開發和驗證想法,證明產品思路是否可行。這個階段:
- 功能設計一般不會太複雜
- 開發採取快速迭代的方式
- 架構不適合過度設計
將所有功能打包部署在一起,集中地進行開發、測試和運維,對於項目起步階段是最高效也是最節省成本的方式。
第二階段:功能擴張期
當可行性驗證通過,功能進一步迭代:
- 需要加入越來越多的新特性
- 需要大規模地擴張開發人員
- 多個功能模組混雜在一起開發、測試和部署會導致相互影響
拆分的信號指標#
| 信號 | 說明 |
|---|---|
| 團隊規模超過 10 人 | 同時進行開發的人員過多,協調成本急劇上升 |
| 發布頻率衝突 | 不同功能的發布週期不同,但被迫一起發布 |
| 故障影響擴大 | 一個模組的問題導致全站不可用 |
| 技術棧受限 | 某些功能需要不同的技術棧但無法獨立選擇 |
經典案例:某視頻 App 因短時間內某個付費視頻訪問量巨大,超過伺服器承載能力。由於付費視頻和免費視頻的服務部署在一起,導致免費視頻也受波及,幾乎全站崩潰。
服務化拆分的兩種姿勢#
縱向拆分:按業務維度#
縱向拆分是從業務維度進行拆分,標準是按照業務的關聯程度來決定:
- 關聯比較密切的業務適合拆分為一個微服務
- 功能相對比較獨立的業務適合單獨拆分為一個微服務
案例:社交 App 拆分
單體應用
├── 首頁資訊流
├── 評論功能
├── 訊息通知
└── 個人主頁
↓ 縱向拆分
獨立服務
├── 資訊流服務
├── 評論服務
├── 訊息服務
└── 個人主頁服務橫向拆分:按公共功能維度#
橫向拆分是從公共且獨立功能維度拆分,標準是:
- 是否有公共的被多個其他服務呼叫
- 依賴的資源獨立不與其他業務耦合
案例:用戶暱稱功能
無論是首頁資訊流、評論、訊息箱還是個人主頁,都需要顯示用戶的暱稱。如果用戶暱稱功能有產品需求變更:
- 不拆分:需要上線幾乎所有的服務,成本很高
- 拆分後:只需要上線用戶暱稱服務,其他服務不受影響
拆分決策矩陣
| 條件 | 縱向拆分 | 橫向拆分 |
|---|---|---|
| 業務獨立性強 | 適用 | 不適用 |
| 被多個業務共用 | 不適用 | 適用 |
| 變更頻率不同 | 適用 | 適用 |
| 資源依賴獨立 | 適用 | 適用 |
| 技術棧需求不同 | 適用 | 適用 |
服務拆分的前置條件#
業務系統引入新技術必然會帶來架構的複雜度提升。在具體決策前,必須認識到新架構會帶來哪些新的問題,這些問題團隊是否能夠解決。
必須解決的五個問題#
1. 服務如何定義
單體應用中不同功能模組通過類庫方式提供功能。微服務中每個服務運行在各自的進程中,需要通過接口來描述服務:
- 接口名
- 接口參數
- 接口返回值
- 其他描述資訊
2. 服務如何發布和訂閱
拆分為微服務獨立部署後:
- 服務提供者該如何對外暴露自己的地址
- 服務呼叫者該如何查詢所需要呼叫的服務的地址
這需要一個註冊中心來記錄每個服務提供者的地址供服務呼叫者查詢。
3. 服務如何監控
通常對於一個服務,最關心的指標:
- QPS:呼叫量,每秒查詢次數
- AvgTime:平均耗時
- P999:99.9% 的請求效能在多少毫秒以內
需要一種通用的監控方案,覆蓋業務埋點、資料收集、資料處理到資料展示的全鏈路功能。
4. 服務如何治理
拆分為微服務架構後,服務數量變多、依賴關係變複雜:
- 一個服務效能有問題時,依賴的服務都會受影響
- 需要設定呼叫效能閾值,超過時直接返回(熔斷)
- 這是服務治理最常用的手段之一
5. 故障如何定位
一次用戶呼叫可能依賴多個服務,每個服務部署在不同的節點上:
- 需要對用戶請求進行標記
- 在多個依賴的服務系統中繼續傳遞
- 串聯所有路徑,從而進行故障定位
服務分層策略#
雙層服務分層體系#
為了便於理解與架構溝通,建議採用簡潔清晰的「雙層服務分層體系」:
flowchart TB
subgraph 外部接入
E1[PC]
E2[移動端]
E3[第三方]
end
subgraph 第二層["第二層:聚合服務 (Aggregation)"]
A1[PC 聚合服務]
A2[移動聚合服務]
A3[開放平台]
end
subgraph 第一層["第一層:基礎服務 (Basic Services)"]
B1[商品服務]
B2[訂單服務]
B3[用戶服務]
B4[支付服務]
end
E1 --> A1
E2 --> A2
E3 --> A3
A1 --> B1 & B2 & B3
A2 --> B1 & B2 & B3
A3 --> B2 & B4
style 外部接入 fill:#fff3e0
style 第二層 fill:#e3f2fd
style 第一層 fill:#e8f5e9第一層:基礎服務 (Basic Services)#
這一層位於基礎設施之上,是平台最為基礎的支撐服務。
特徵:
- 原子性:每個服務完成一個獨立的業務功能
- 通用性:可以被多個上層服務呼叫
- 穩定性:接口相對穩定,不頻繁變更
典型案例(電商):
- 商品服務
- 訂單服務
- 用戶服務
- 購物車服務
業界常見術語對照
不同公司對這一層有不同的稱呼,但本質相同:
- 核心領域服務 (Core Domain Services)
- 公共服務 (Public Services)
- 中間層服務 (Middle Tier Services):Netflix 體系中的稱呼
第二層:聚合服務 (Aggregation Services)#
為什麼需要聚合服務?因為底層服務傾向於通用與抽象,而外部接入端(PC、手機)的需求是具體且多變的。
核心功能一:適配與裁剪
不同終端設備對資料的呈現需求不同:
- PC 端:螢幕較大,展示資訊豐富
- 移動端:螢幕較小、帶寬有限,需要對基礎服務返回的 Payload 進行「裁剪」
核心功能二:服務聚合
單一頁面往往需要展示來自多個基礎服務的資訊:
無聚合層時:
客戶端 ──→ 商品服務
──→ 分類服務
──→ 購物車服務
(多次網路請求,延遲高)
有聚合層時:
客戶端 ──→ 聚合服務 ──→ 商品服務
──→ 分類服務
──→ 購物車服務
(一次請求,內網並行呼叫)有些公司稱此層為 Adapter Service(適配服務),Netflix 則稱之為 Edge Service(邊界服務),因為它處於公司內網與外網的邊界上。
拆分粒度的把握#
拆分原則#
並不是功能拆分得越細越好。過度的拆分反而會讓服務數量膨脹變得難以管理。
建議的標準:按照每個開發人員負責不超過 3 個大的服務為標準。
根據開發人員的總人數來決定拆分粒度:
- 10 人團隊 → 約 30 個服務
- 50 人團隊 → 約 150 個服務
避免的反模式#
反模式一:過早拆分
在對業務領域理解不深時就進行拆分,導致:
- 服務邊界劃分錯誤
- 頻繁的跨服務重構
- 分散式系統的複雜度卻沒有帶來相應收益
反模式二:按技術層拆分
錯誤地按照技術層次(如 DAO 層、Service 層、Controller 層)進行拆分,而不是按業務能力拆分。
反模式三:忽視資料邊界
拆分服務時沒有同時考慮資料的拆分,導致:
- 多個服務共享同一個資料庫
- 資料庫成為耦合點
- 無法獨立擴展和部署
正確的拆分步驟
- 理解業務領域:深入理解業務流程和領域邊界
- 識別有界上下文:確定每個服務的資料邊界
- 定義服務接口:明確服務間的交互契約
- 評估依賴關係:確保服務間的依賴是合理的
- 規劃資料遷移:制定資料拆分和遷移策略
- 漸進式拆分:從風險最低的模組開始,逐步推進
實踐建議#
拆分決策清單#
在決定拆分某個模組之前,回答以下問題:
| 問題 | 期望答案 |
|---|---|
| 這個模組的業務邊界是否清晰? | 是 |
| 團隊對這個領域的理解是否足夠深入? | 是 |
| 拆分後服務的接口是否已經明確定義? | 是 |
| 相關的資料是否可以獨立? | 是 |
| 團隊是否具備分散式系統的運維能力? | 是 |
| 是否有足夠的監控和追蹤基礎設施? | 是 |
漸進式拆分策略#
推薦的做法是漸進式拆分,而不是一步到位的大爆炸式重構。
步驟一:絞殺者模式 (Strangler Pattern)
在單體應用外部構建新的微服務,逐步將流量從舊系統遷移到新系統。
步驟二:從邊緣開始
選擇風險最低、依賴最少的模組開始拆分,積累經驗。
步驟三:建立基礎設施
在大規模拆分之前,確保:
- 註冊中心已就緒
- 監控系統已就緒
- 日誌系統已就緒
- CI/CD 流水線已就緒
步驟四:持續驗證
每完成一個服務的拆分:
- 驗證功能正確性
- 驗證效能指標
- 評估運維複雜度
- 總結經驗教訓