微服務 vs 單體架構:如何做出正確的技術選擇
6 分鐘閱讀 1,100 字
微服務 vs 單體架構:如何做出正確的技術選擇
「我們要改成微服務架構!」這句話在過去幾年的技術討論中頻繁出現。微服務被描繪成現代化、可擴展的架構,彷彿不用就落伍了。但真相是:許多採用微服務的團隊正在悄悄地把服務合併回去。本文試圖客觀地分析兩種架構的優缺點,幫助你做出適合自己團隊的選擇。
什麼是單體架構?
單體架構(Monolith)是把所有功能打包在同一個部署單元的架構:
單體應用
├── 使用者模組
├── 訂單模組
├── 商品模組
├── 支付模組
└── 通知模組
↓ 統一建置、統一部署、共用資料庫單體不等於「舊」或「差」。Google、Facebook、Twitter 都是從單體開始的,Netflix 直到流量大到一定規模才開始拆分。
什麼是微服務架構?
微服務把每個業務功能拆分為獨立運行的服務,透過 API 或訊息佇列通訊:
微服務架構
├── user-service (port 3001)
├── order-service (port 3002)
├── product-service (port 3003)
├── payment-service (port 3004)
└── notification-service (port 3005)
↓ 各自獨立部署、可獨立擴展、各自的資料庫單體架構的優勢
1. 開發效率高
在單體中,跨模組的函式呼叫就是直接調用:
// 單體中的訂單服務
class OrderService {
constructor(
private readonly productService: ProductService,
private readonly paymentService: PaymentService,
private readonly notificationService: NotificationService
) {}
async createOrder(userId: string, items: OrderItem[]) {
// 直接呼叫,沒有網路開銷、沒有序列化
const products = await this.productService.validateStock(items);
const payment = await this.paymentService.charge(userId, products.total);
await this.notificationService.sendConfirmation(userId, payment.id);
return this.orderRepository.create({ userId, items, paymentId: payment.id });
}
}2. 除錯和測試簡單
一個 stack trace 就能追蹤整個請求的生命週期,不需要分散式追蹤工具。
3. 部署簡單
# 單體的 docker-compose.yml
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://...
db:
image: postgres:164. ACID 事務天然支援
// 在同一個資料庫事務中完成所有操作
async function checkout(userId: string, cartItems: CartItem[]) {
return await db.transaction(async (trx) => {
await trx('inventory').decrement('stock', cartItems);
const order = await trx('orders').insert({ userId });
await trx('payments').insert({ orderId: order.id, amount: total });
// 任何步驟失敗,整個事務回滾
});
}微服務的優勢
1. 獨立擴展
# Kubernetes 中只擴展高負載的服務
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: product-service-hpa
spec:
scaleTargetRef:
name: product-service
minReplicas: 2
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
averageUtilization: 602. 技術多樣性
每個服務可以選擇最適合的技術棧:
- 搜尋服務:Elasticsearch + Python
- 即時通訊:Node.js + WebSocket
- 資料分析:Spark + Scala
- 一般業務:TypeScript + PostgreSQL
3. 獨立部署
支付團隊可以在不影響其他服務的情況下部署更新。
4. 故障隔離
// 使用 Circuit Breaker 防止級聯故障
import { CircuitBreaker } from 'opossum';
const breaker = new CircuitBreaker(callPaymentService, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
breaker.fallback(() => ({ status: 'queued', message: '付款將稍後處理' }));
async function processOrder(order: Order) {
// 即使 payment-service 掛掉,訂單系統仍可運行
const paymentResult = await breaker.fire(order.paymentInfo);
...
}微服務的隱藏成本
這些成本常被忽略:
分散式系統的複雜性
// 微服務中的「簡單」訂單建立
async function createOrder(userId: string, items: OrderItem[]) {
// 每一步都可能失敗、超時、回傳過時的資料
let stockReserved = false;
let paymentCharged = false;
try {
// 1. 檢查庫存(可能失敗)
await inventoryService.reserve(items); // 跨網路呼叫
stockReserved = true;
// 2. 扣款(可能失敗,但庫存已被鎖定)
await paymentService.charge(userId, total); // 跨網路呼叫
paymentCharged = true;
// 3. 建立訂單(可能失敗,但庫存和款項都動了)
return await orderService.create({ userId, items });
} catch (error) {
// Saga 補償事務:需要逆向操作來恢復狀態
if (paymentCharged) await paymentService.refund(userId, total);
if (stockReserved) await inventoryService.release(items);
throw error;
}
}基礎設施成本
你需要:
- 服務發現:Consul, etcd
- API 閘道:Kong, AWS API Gateway
- 分散式追蹤:Jaeger, Zipkin
- 集中式日誌:ELK Stack
- 訊息佇列:Kafka, RabbitMQ
- 服務網格:Istio(可選)
這些工具本身的學習成本和維護成本相當可觀。
決策框架
你的團隊有多少工程師?
├── < 10 人 → 先做單體,等真的需要再拆
├── 10-50 人 → 模組化單體可能更合適
└── > 50 人,且業務界限清晰 → 考慮微服務
你的主要瓶頸是什麼?
├── 部署頻率衝突 → 微服務有幫助
├── 特定功能需要大量擴展 → 微服務有幫助
├── 開發速度慢 → 微服務通常讓事情更慢
└── 技術棧限制 → 微服務有幫助中間路線:模組化單體
模組化單體(Modular Monolith)是一個被低估的選擇:
// 嚴格的模組邊界,但仍是單一部署
// 每個模組只能透過公開介面存取
// modules/orders/index.ts(公開 API)
export { OrderService } from './OrderService';
export type { Order, CreateOrderDTO } from './types';
// modules/orders/internal/(外部不可存取)
// 資料庫 schema、內部邏輯等
// 如果未來需要拆分,邊界已經清晰,遷移成本最小真實案例參考
- Amazon:大約在 2002 年開始拆分微服務,當時有數千名工程師
- Netflix:2008-2012 年漸進式拆分,由於 DVD 業務和串流業務有本質差異
- Shopify:直到 2020 年還是模組化單體,後來才引入部分微服務
- Stack Overflow:至今仍是單體,每分鐘處理數百萬請求
小結
微服務解決的是規模問題,而不是技術問題。如果你的團隊還在為「功能開發速度太慢」煩惱,微服務只會讓它更慢。先建立好的工程實踐、清晰的模組邊界、完善的測試覆蓋率——這些在任何架構下都有價值。當你真的感受到單體的瓶頸時,再做有針對性的拆分,才是務實的工程決策。
分享這篇文章